@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/dist/index.cjs +595 -12
- package/dist/index.d.cts +566 -2
- package/dist/index.d.ts +566 -2
- package/dist/index.js +586 -11
- package/package.json +7 -7
- package/src/cache/MetadataCache.ts +22 -0
- package/src/cloud.ts +109 -0
- package/src/connection.test.ts +41 -0
- package/src/contracts.ts +115 -0
- package/src/errors.test.ts +1 -1
- package/src/index.ts +290 -12
- package/src/integration.ts +192 -0
- package/src/security.ts +230 -0
- package/src/studio.ts +152 -0
- package/src/upload.test.ts +112 -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) {
|
|
@@ -345,11 +707,15 @@ var ObjectStackAdapter = class {
|
|
|
345
707
|
__publicField(this, "maxReconnectAttempts");
|
|
346
708
|
__publicField(this, "reconnectDelay");
|
|
347
709
|
__publicField(this, "reconnectAttempts", 0);
|
|
710
|
+
__publicField(this, "baseUrl");
|
|
711
|
+
__publicField(this, "token");
|
|
348
712
|
this.client = new ObjectStackClient(config);
|
|
349
713
|
this.metadataCache = new MetadataCache(config.cache);
|
|
350
714
|
this.autoReconnect = config.autoReconnect ?? true;
|
|
351
715
|
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 3;
|
|
352
716
|
this.reconnectDelay = config.reconnectDelay ?? 1e3;
|
|
717
|
+
this.baseUrl = config.baseUrl;
|
|
718
|
+
this.token = config.token;
|
|
353
719
|
}
|
|
354
720
|
/**
|
|
355
721
|
* Ensure the client is connected to the server.
|
|
@@ -474,13 +840,15 @@ var ObjectStackAdapter = class {
|
|
|
474
840
|
};
|
|
475
841
|
}
|
|
476
842
|
const resultObj = result;
|
|
843
|
+
const records = resultObj.records || resultObj.value || [];
|
|
844
|
+
const total = resultObj.total ?? resultObj.count ?? records.length;
|
|
477
845
|
return {
|
|
478
|
-
data:
|
|
479
|
-
total
|
|
846
|
+
data: records,
|
|
847
|
+
total,
|
|
480
848
|
// Calculate page number safely
|
|
481
849
|
page: params?.$skip && params.$top ? Math.floor(params.$skip / params.$top) + 1 : 1,
|
|
482
850
|
pageSize: params?.$top,
|
|
483
|
-
hasMore: params?.$top ?
|
|
851
|
+
hasMore: params?.$top ? records.length === params.$top : false
|
|
484
852
|
};
|
|
485
853
|
}
|
|
486
854
|
/**
|
|
@@ -489,8 +857,8 @@ var ObjectStackAdapter = class {
|
|
|
489
857
|
async findOne(resource, id, _params) {
|
|
490
858
|
await this.connect();
|
|
491
859
|
try {
|
|
492
|
-
const
|
|
493
|
-
return record;
|
|
860
|
+
const result = await this.client.data.get(resource, String(id));
|
|
861
|
+
return result.record;
|
|
494
862
|
} catch (error) {
|
|
495
863
|
if (error?.status === 404) {
|
|
496
864
|
return null;
|
|
@@ -503,14 +871,16 @@ var ObjectStackAdapter = class {
|
|
|
503
871
|
*/
|
|
504
872
|
async create(resource, data) {
|
|
505
873
|
await this.connect();
|
|
506
|
-
|
|
874
|
+
const result = await this.client.data.create(resource, data);
|
|
875
|
+
return result.record;
|
|
507
876
|
}
|
|
508
877
|
/**
|
|
509
878
|
* Update an existing record.
|
|
510
879
|
*/
|
|
511
880
|
async update(resource, id, data) {
|
|
512
881
|
await this.connect();
|
|
513
|
-
|
|
882
|
+
const result = await this.client.data.update(resource, String(id), data);
|
|
883
|
+
return result.record;
|
|
514
884
|
}
|
|
515
885
|
/**
|
|
516
886
|
* Delete a record.
|
|
@@ -518,7 +888,7 @@ var ObjectStackAdapter = class {
|
|
|
518
888
|
async delete(resource, id) {
|
|
519
889
|
await this.connect();
|
|
520
890
|
const result = await this.client.data.delete(resource, String(id));
|
|
521
|
-
return result.
|
|
891
|
+
return result.deleted;
|
|
522
892
|
}
|
|
523
893
|
/**
|
|
524
894
|
* Bulk operations with optimized batch processing and error handling.
|
|
@@ -600,7 +970,7 @@ var ObjectStackAdapter = class {
|
|
|
600
970
|
}
|
|
601
971
|
try {
|
|
602
972
|
const result = await this.client.data.update(resource, String(id), item);
|
|
603
|
-
results.push(result);
|
|
973
|
+
results.push(result.record);
|
|
604
974
|
completed++;
|
|
605
975
|
emitProgress();
|
|
606
976
|
} catch (error) {
|
|
@@ -701,7 +1071,7 @@ var ObjectStackAdapter = class {
|
|
|
701
1071
|
await this.connect();
|
|
702
1072
|
try {
|
|
703
1073
|
const schema = await this.metadataCache.get(objectName, async () => {
|
|
704
|
-
const result = await this.client.meta.
|
|
1074
|
+
const result = await this.client.meta.getItem("object", objectName);
|
|
705
1075
|
if (result && result.item) {
|
|
706
1076
|
return result.item;
|
|
707
1077
|
}
|
|
@@ -725,6 +1095,111 @@ var ObjectStackAdapter = class {
|
|
|
725
1095
|
getClient() {
|
|
726
1096
|
return this.client;
|
|
727
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Get the discovery information from the connected server.
|
|
1100
|
+
* Returns the capabilities and service status of the ObjectStack server.
|
|
1101
|
+
*
|
|
1102
|
+
* Note: This accesses an internal property of the ObjectStackClient.
|
|
1103
|
+
* The discovery data is populated during client.connect() and cached.
|
|
1104
|
+
*
|
|
1105
|
+
* @returns Promise resolving to discovery data, or null if not connected
|
|
1106
|
+
*/
|
|
1107
|
+
async getDiscovery() {
|
|
1108
|
+
try {
|
|
1109
|
+
await this.connect();
|
|
1110
|
+
return this.client.discoveryInfo || null;
|
|
1111
|
+
} catch {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Get a view definition for an object.
|
|
1117
|
+
* Attempts to fetch from the server metadata API.
|
|
1118
|
+
* Falls back to null if the server doesn't provide view definitions,
|
|
1119
|
+
* allowing the consumer to use static config.
|
|
1120
|
+
*
|
|
1121
|
+
* @param objectName - Object name
|
|
1122
|
+
* @param viewId - View identifier
|
|
1123
|
+
* @returns Promise resolving to the view definition or null
|
|
1124
|
+
*/
|
|
1125
|
+
async getView(objectName, viewId) {
|
|
1126
|
+
await this.connect();
|
|
1127
|
+
try {
|
|
1128
|
+
const cacheKey = `view:${objectName}:${viewId}`;
|
|
1129
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1130
|
+
const result = await this.client.meta.getItem(objectName, `views/${viewId}`);
|
|
1131
|
+
if (result && result.item) return result.item;
|
|
1132
|
+
return result ?? null;
|
|
1133
|
+
});
|
|
1134
|
+
} catch {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Get an application definition by name or ID.
|
|
1140
|
+
* Attempts to fetch from the server metadata API.
|
|
1141
|
+
* Falls back to null if the server doesn't provide app definitions,
|
|
1142
|
+
* allowing the consumer to use static config.
|
|
1143
|
+
*
|
|
1144
|
+
* @param appId - Application identifier
|
|
1145
|
+
* @returns Promise resolving to the app definition or null
|
|
1146
|
+
*/
|
|
1147
|
+
async getApp(appId) {
|
|
1148
|
+
await this.connect();
|
|
1149
|
+
try {
|
|
1150
|
+
const cacheKey = `app:${appId}`;
|
|
1151
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1152
|
+
const result = await this.client.meta.getItem("apps", appId);
|
|
1153
|
+
if (result && result.item) return result.item;
|
|
1154
|
+
return result ?? null;
|
|
1155
|
+
});
|
|
1156
|
+
} catch {
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
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
|
+
}
|
|
728
1203
|
/**
|
|
729
1204
|
* Get cache statistics for monitoring performance.
|
|
730
1205
|
*/
|
|
@@ -745,6 +1220,98 @@ var ObjectStackAdapter = class {
|
|
|
745
1220
|
clearCache() {
|
|
746
1221
|
this.metadataCache.clear();
|
|
747
1222
|
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Upload a single file to a resource.
|
|
1225
|
+
* Posts the file as multipart/form-data to the ObjectStack server.
|
|
1226
|
+
*
|
|
1227
|
+
* @param resource - The resource/object name to attach the file to
|
|
1228
|
+
* @param file - File object or Blob to upload
|
|
1229
|
+
* @param options - Additional upload options (recordId, fieldName, metadata)
|
|
1230
|
+
* @returns Promise resolving to the upload result (file URL, metadata)
|
|
1231
|
+
*/
|
|
1232
|
+
async uploadFile(resource, file, options) {
|
|
1233
|
+
await this.connect();
|
|
1234
|
+
const formData = new FormData();
|
|
1235
|
+
formData.append("file", file);
|
|
1236
|
+
if (options?.recordId) {
|
|
1237
|
+
formData.append("recordId", options.recordId);
|
|
1238
|
+
}
|
|
1239
|
+
if (options?.fieldName) {
|
|
1240
|
+
formData.append("fieldName", options.fieldName);
|
|
1241
|
+
}
|
|
1242
|
+
if (options?.metadata) {
|
|
1243
|
+
formData.append("metadata", JSON.stringify(options.metadata));
|
|
1244
|
+
}
|
|
1245
|
+
const url = `${this.baseUrl}/api/data/${encodeURIComponent(resource)}/upload`;
|
|
1246
|
+
const response = await fetch(url, {
|
|
1247
|
+
method: "POST",
|
|
1248
|
+
body: formData,
|
|
1249
|
+
headers: {
|
|
1250
|
+
...this.getAuthHeaders()
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
if (!response.ok) {
|
|
1254
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
1255
|
+
throw new ObjectStackError(
|
|
1256
|
+
error.message || `Upload failed with status ${response.status}`,
|
|
1257
|
+
"UPLOAD_ERROR",
|
|
1258
|
+
response.status
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
return response.json();
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Upload multiple files to a resource.
|
|
1265
|
+
* Posts all files as a single multipart/form-data request.
|
|
1266
|
+
*
|
|
1267
|
+
* @param resource - The resource/object name to attach the files to
|
|
1268
|
+
* @param files - Array of File objects or Blobs to upload
|
|
1269
|
+
* @param options - Additional upload options
|
|
1270
|
+
* @returns Promise resolving to array of upload results
|
|
1271
|
+
*/
|
|
1272
|
+
async uploadFiles(resource, files, options) {
|
|
1273
|
+
await this.connect();
|
|
1274
|
+
const formData = new FormData();
|
|
1275
|
+
files.forEach((file, idx) => {
|
|
1276
|
+
formData.append(`files`, file, file.name || `file-${idx}`);
|
|
1277
|
+
});
|
|
1278
|
+
if (options?.recordId) {
|
|
1279
|
+
formData.append("recordId", options.recordId);
|
|
1280
|
+
}
|
|
1281
|
+
if (options?.fieldName) {
|
|
1282
|
+
formData.append("fieldName", options.fieldName);
|
|
1283
|
+
}
|
|
1284
|
+
if (options?.metadata) {
|
|
1285
|
+
formData.append("metadata", JSON.stringify(options.metadata));
|
|
1286
|
+
}
|
|
1287
|
+
const url = `${this.baseUrl}/api/data/${encodeURIComponent(resource)}/upload`;
|
|
1288
|
+
const response = await fetch(url, {
|
|
1289
|
+
method: "POST",
|
|
1290
|
+
body: formData,
|
|
1291
|
+
headers: {
|
|
1292
|
+
...this.getAuthHeaders()
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
if (!response.ok) {
|
|
1296
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
1297
|
+
throw new ObjectStackError(
|
|
1298
|
+
error.message || `Upload failed with status ${response.status}`,
|
|
1299
|
+
"UPLOAD_ERROR",
|
|
1300
|
+
response.status
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
return response.json();
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Get authorization headers from the adapter config.
|
|
1307
|
+
*/
|
|
1308
|
+
getAuthHeaders() {
|
|
1309
|
+
const headers = {};
|
|
1310
|
+
if (this.token) {
|
|
1311
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
1312
|
+
}
|
|
1313
|
+
return headers;
|
|
1314
|
+
}
|
|
748
1315
|
};
|
|
749
1316
|
function createObjectStackAdapter(config) {
|
|
750
1317
|
return new ObjectStackAdapter(config);
|
|
@@ -752,13 +1319,21 @@ function createObjectStackAdapter(config) {
|
|
|
752
1319
|
export {
|
|
753
1320
|
AuthenticationError,
|
|
754
1321
|
BulkOperationError,
|
|
1322
|
+
CloudOperations,
|
|
755
1323
|
ConnectionError,
|
|
1324
|
+
IntegrationManager,
|
|
756
1325
|
MetadataNotFoundError,
|
|
757
1326
|
ObjectStackAdapter,
|
|
758
1327
|
ObjectStackError,
|
|
1328
|
+
SecurityManager,
|
|
759
1329
|
ValidationError,
|
|
1330
|
+
calculateAutoLayout,
|
|
1331
|
+
createDefaultCanvasConfig,
|
|
760
1332
|
createErrorFromResponse,
|
|
761
1333
|
createObjectStackAdapter,
|
|
1334
|
+
generateContractManifest,
|
|
762
1335
|
isErrorType,
|
|
763
|
-
isObjectStackError
|
|
1336
|
+
isObjectStackError,
|
|
1337
|
+
snapToGrid,
|
|
1338
|
+
validatePluginContract
|
|
764
1339
|
};
|