@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.cjs
CHANGED
|
@@ -24,15 +24,23 @@ var index_exports = {};
|
|
|
24
24
|
__export(index_exports, {
|
|
25
25
|
AuthenticationError: () => AuthenticationError,
|
|
26
26
|
BulkOperationError: () => BulkOperationError,
|
|
27
|
+
CloudOperations: () => CloudOperations,
|
|
27
28
|
ConnectionError: () => ConnectionError,
|
|
29
|
+
IntegrationManager: () => IntegrationManager,
|
|
28
30
|
MetadataNotFoundError: () => MetadataNotFoundError,
|
|
29
31
|
ObjectStackAdapter: () => ObjectStackAdapter,
|
|
30
32
|
ObjectStackError: () => ObjectStackError,
|
|
33
|
+
SecurityManager: () => SecurityManager,
|
|
31
34
|
ValidationError: () => ValidationError,
|
|
35
|
+
calculateAutoLayout: () => calculateAutoLayout,
|
|
36
|
+
createDefaultCanvasConfig: () => createDefaultCanvasConfig,
|
|
32
37
|
createErrorFromResponse: () => createErrorFromResponse,
|
|
33
38
|
createObjectStackAdapter: () => createObjectStackAdapter,
|
|
39
|
+
generateContractManifest: () => generateContractManifest,
|
|
34
40
|
isErrorType: () => isErrorType,
|
|
35
|
-
isObjectStackError: () => isObjectStackError
|
|
41
|
+
isObjectStackError: () => isObjectStackError,
|
|
42
|
+
snapToGrid: () => snapToGrid,
|
|
43
|
+
validatePluginContract: () => validatePluginContract
|
|
36
44
|
});
|
|
37
45
|
module.exports = __toCommonJS(index_exports);
|
|
38
46
|
var import_client = require("@objectstack/client");
|
|
@@ -157,6 +165,22 @@ var MetadataCache = class {
|
|
|
157
165
|
hitRate
|
|
158
166
|
};
|
|
159
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Get a cached value synchronously without triggering a fetch.
|
|
170
|
+
* Returns undefined if not in cache or expired.
|
|
171
|
+
*/
|
|
172
|
+
getCachedSync(key) {
|
|
173
|
+
const entry = this.cache.get(key);
|
|
174
|
+
if (!entry) return void 0;
|
|
175
|
+
if (this.ttl > 0 && Date.now() - entry.timestamp > this.ttl) {
|
|
176
|
+
this.cache.delete(key);
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
this.cache.delete(key);
|
|
180
|
+
this.cache.set(key, entry);
|
|
181
|
+
this.stats.hits++;
|
|
182
|
+
return entry.data;
|
|
183
|
+
}
|
|
160
184
|
/**
|
|
161
185
|
* Check if a key exists in the cache (and is not expired)
|
|
162
186
|
*
|
|
@@ -364,6 +388,352 @@ function isErrorType(error, errorClass) {
|
|
|
364
388
|
return error instanceof errorClass;
|
|
365
389
|
}
|
|
366
390
|
|
|
391
|
+
// src/cloud.ts
|
|
392
|
+
var CloudOperations = class {
|
|
393
|
+
constructor(getClient) {
|
|
394
|
+
this.getClient = getClient;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Deploy an application to the cloud.
|
|
398
|
+
*/
|
|
399
|
+
async deploy(appId, config) {
|
|
400
|
+
const client = this.getClient();
|
|
401
|
+
const result = await client.cloud?.deploy?.(appId, config);
|
|
402
|
+
return result ?? { deploymentId: `deploy-${Date.now()}`, status: "pending" };
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Get deployment status.
|
|
406
|
+
*/
|
|
407
|
+
async getDeploymentStatus(deploymentId) {
|
|
408
|
+
const client = this.getClient();
|
|
409
|
+
const result = await client.cloud?.getDeployment?.(deploymentId);
|
|
410
|
+
return result ?? { status: "unknown" };
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Search marketplace entries.
|
|
414
|
+
*/
|
|
415
|
+
async searchMarketplace(query, category) {
|
|
416
|
+
const client = this.getClient();
|
|
417
|
+
const result = await client.cloud?.marketplace?.search?.({ query, category });
|
|
418
|
+
return result?.items ?? [];
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Install a marketplace plugin.
|
|
422
|
+
*/
|
|
423
|
+
async installPlugin(pluginId) {
|
|
424
|
+
const client = this.getClient();
|
|
425
|
+
const result = await client.cloud?.marketplace?.install?.(pluginId);
|
|
426
|
+
return result ?? { success: false };
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/contracts.ts
|
|
431
|
+
function validatePluginContract(contract) {
|
|
432
|
+
const errors = [];
|
|
433
|
+
const warnings = [];
|
|
434
|
+
if (!contract.name || contract.name.trim().length === 0) {
|
|
435
|
+
errors.push({ field: "name", message: "Plugin name is required", code: "MISSING_NAME" });
|
|
436
|
+
}
|
|
437
|
+
if (!contract.version || !/^\d+\.\d+\.\d+/.test(contract.version)) {
|
|
438
|
+
errors.push({ field: "version", message: "Valid semver version is required", code: "INVALID_VERSION" });
|
|
439
|
+
}
|
|
440
|
+
if (!contract.exports || contract.exports.length === 0) {
|
|
441
|
+
errors.push({ field: "exports", message: "At least one export is required", code: "NO_EXPORTS" });
|
|
442
|
+
}
|
|
443
|
+
if (contract.exports) {
|
|
444
|
+
const validTypes = ["component", "hook", "utility", "provider"];
|
|
445
|
+
for (const exp of contract.exports) {
|
|
446
|
+
if (!exp.name) {
|
|
447
|
+
errors.push({ field: "exports.name", message: "Export name is required", code: "MISSING_EXPORT_NAME" });
|
|
448
|
+
}
|
|
449
|
+
if (!validTypes.includes(exp.type)) {
|
|
450
|
+
errors.push({ field: "exports.type", message: `Invalid export type: ${exp.type}`, code: "INVALID_EXPORT_TYPE" });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (!contract.permissions || contract.permissions.length === 0) {
|
|
455
|
+
warnings.push("No permissions declared \u2014 plugin will have minimal access");
|
|
456
|
+
}
|
|
457
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
458
|
+
}
|
|
459
|
+
function generateContractManifest(contract) {
|
|
460
|
+
return {
|
|
461
|
+
$schema: "https://objectui.org/schemas/plugin-contract-v1.json",
|
|
462
|
+
name: contract.name,
|
|
463
|
+
version: contract.version,
|
|
464
|
+
peerDependencies: contract.peerDependencies ?? {},
|
|
465
|
+
exports: contract.exports.map((exp) => ({
|
|
466
|
+
name: exp.name,
|
|
467
|
+
type: exp.type,
|
|
468
|
+
description: exp.description ?? ""
|
|
469
|
+
})),
|
|
470
|
+
permissions: contract.permissions ?? [],
|
|
471
|
+
api: contract.api ?? {},
|
|
472
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/integration.ts
|
|
477
|
+
var IntegrationManager = class {
|
|
478
|
+
constructor() {
|
|
479
|
+
__publicField(this, "integrations", /* @__PURE__ */ new Map());
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Register a new integration.
|
|
483
|
+
*/
|
|
484
|
+
register(id, config) {
|
|
485
|
+
this.integrations.set(id, config);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Remove an integration.
|
|
489
|
+
*/
|
|
490
|
+
unregister(id) {
|
|
491
|
+
this.integrations.delete(id);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get all registered integrations.
|
|
495
|
+
*/
|
|
496
|
+
getAll() {
|
|
497
|
+
return new Map(this.integrations);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get integrations that match a specific event.
|
|
501
|
+
*/
|
|
502
|
+
getForEvent(event) {
|
|
503
|
+
return Array.from(this.integrations.values()).filter(
|
|
504
|
+
(integration) => integration.enabled && integration.triggers?.some((t) => t.event === event)
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Dispatch an event to all matching integrations.
|
|
509
|
+
* Returns results for each integration.
|
|
510
|
+
*/
|
|
511
|
+
async dispatch(event, payload) {
|
|
512
|
+
const matching = this.getForEvent(event);
|
|
513
|
+
const results = [];
|
|
514
|
+
for (const [id, integration] of this.integrations) {
|
|
515
|
+
if (!matching.includes(integration)) continue;
|
|
516
|
+
try {
|
|
517
|
+
await this.send(integration, payload);
|
|
518
|
+
results.push({ id, success: true });
|
|
519
|
+
} catch (err) {
|
|
520
|
+
results.push({ id, success: false, error: err.message });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return results;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Send payload to a specific integration.
|
|
527
|
+
*/
|
|
528
|
+
async send(integration, payload) {
|
|
529
|
+
switch (integration.provider) {
|
|
530
|
+
case "webhook": {
|
|
531
|
+
const cfg = integration.config;
|
|
532
|
+
const url = cfg.url;
|
|
533
|
+
try {
|
|
534
|
+
const parsed = new URL(url);
|
|
535
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
536
|
+
throw new Error(`Invalid URL protocol: ${parsed.protocol}`);
|
|
537
|
+
}
|
|
538
|
+
} catch (e) {
|
|
539
|
+
if (e instanceof TypeError) {
|
|
540
|
+
throw new Error(`Invalid webhook URL: ${url}`);
|
|
541
|
+
}
|
|
542
|
+
throw e;
|
|
543
|
+
}
|
|
544
|
+
await fetch(url, {
|
|
545
|
+
method: cfg.method,
|
|
546
|
+
headers: {
|
|
547
|
+
"Content-Type": "application/json",
|
|
548
|
+
...cfg.headers
|
|
549
|
+
},
|
|
550
|
+
body: JSON.stringify(payload)
|
|
551
|
+
});
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case "slack": {
|
|
555
|
+
const cfg = integration.config;
|
|
556
|
+
const url = cfg.webhookUrl;
|
|
557
|
+
try {
|
|
558
|
+
const parsed = new URL(url);
|
|
559
|
+
if (parsed.protocol !== "https:") {
|
|
560
|
+
throw new Error(`Invalid Slack webhook URL protocol: ${parsed.protocol}`);
|
|
561
|
+
}
|
|
562
|
+
} catch (e) {
|
|
563
|
+
if (e instanceof TypeError) {
|
|
564
|
+
throw new Error(`Invalid Slack webhook URL: ${url}`);
|
|
565
|
+
}
|
|
566
|
+
throw e;
|
|
567
|
+
}
|
|
568
|
+
await fetch(url, {
|
|
569
|
+
method: "POST",
|
|
570
|
+
headers: { "Content-Type": "application/json" },
|
|
571
|
+
body: JSON.stringify({
|
|
572
|
+
channel: cfg.channel,
|
|
573
|
+
username: cfg.username,
|
|
574
|
+
icon_emoji: cfg.iconEmoji,
|
|
575
|
+
text: JSON.stringify(payload)
|
|
576
|
+
})
|
|
577
|
+
});
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
// Email and other providers would require server-side implementation
|
|
581
|
+
default:
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// src/security.ts
|
|
588
|
+
var SecurityManager = class {
|
|
589
|
+
constructor(policy = {}) {
|
|
590
|
+
__publicField(this, "policy");
|
|
591
|
+
__publicField(this, "auditLog", []);
|
|
592
|
+
this.policy = policy;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Generate a CSP header string from the configuration.
|
|
596
|
+
*/
|
|
597
|
+
generateCSPHeader() {
|
|
598
|
+
const csp = this.policy.csp;
|
|
599
|
+
if (!csp) return "";
|
|
600
|
+
const directives = [];
|
|
601
|
+
if (csp.scriptSrc?.length) directives.push(`script-src ${csp.scriptSrc.join(" ")}`);
|
|
602
|
+
if (csp.styleSrc?.length) directives.push(`style-src ${csp.styleSrc.join(" ")}`);
|
|
603
|
+
if (csp.imgSrc?.length) directives.push(`img-src ${csp.imgSrc.join(" ")}`);
|
|
604
|
+
if (csp.connectSrc?.length) directives.push(`connect-src ${csp.connectSrc.join(" ")}`);
|
|
605
|
+
if (csp.fontSrc?.length) directives.push(`font-src ${csp.fontSrc.join(" ")}`);
|
|
606
|
+
if (csp.frameSrc?.length) directives.push(`frame-src ${csp.frameSrc.join(" ")}`);
|
|
607
|
+
if (csp.reportUri) directives.push(`report-uri ${csp.reportUri}`);
|
|
608
|
+
return directives.join("; ");
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Record an audit log entry.
|
|
612
|
+
*/
|
|
613
|
+
recordAudit(entry) {
|
|
614
|
+
if (!this.policy.auditLog?.enabled) return;
|
|
615
|
+
if (this.policy.auditLog.events && !this.policy.auditLog.events.includes(entry.event)) return;
|
|
616
|
+
const fullEntry = {
|
|
617
|
+
...entry,
|
|
618
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
619
|
+
};
|
|
620
|
+
this.auditLog.push(fullEntry);
|
|
621
|
+
const dest = this.policy.auditLog.destination ?? "console";
|
|
622
|
+
if (dest === "console" || dest === "both") {
|
|
623
|
+
console.info("[AUDIT]", fullEntry.event, fullEntry.userId, fullEntry.resource ?? "");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Get audit log entries.
|
|
628
|
+
*/
|
|
629
|
+
getAuditLog(filter) {
|
|
630
|
+
let entries = [...this.auditLog];
|
|
631
|
+
if (filter?.event) entries = entries.filter((e) => e.event === filter.event);
|
|
632
|
+
if (filter?.userId) entries = entries.filter((e) => e.userId === filter.userId);
|
|
633
|
+
if (filter?.since) entries = entries.filter((e) => e.timestamp >= filter.since);
|
|
634
|
+
return entries;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Apply data masking to a record.
|
|
638
|
+
*/
|
|
639
|
+
maskRecord(record, userRoles = []) {
|
|
640
|
+
if (!this.policy.dataMasking?.rules?.length) return record;
|
|
641
|
+
const masked = { ...record };
|
|
642
|
+
const maskChar = this.policy.dataMasking.maskChar ?? "*";
|
|
643
|
+
for (const rule of this.policy.dataMasking.rules) {
|
|
644
|
+
if (!(rule.field in masked) || masked[rule.field] == null) continue;
|
|
645
|
+
if (rule.exemptRoles?.some((role) => userRoles.includes(role))) continue;
|
|
646
|
+
const value = String(masked[rule.field]);
|
|
647
|
+
switch (rule.strategy) {
|
|
648
|
+
case "full":
|
|
649
|
+
masked[rule.field] = maskChar.repeat(value.length);
|
|
650
|
+
break;
|
|
651
|
+
case "partial": {
|
|
652
|
+
const visible = rule.visibleChars ?? 4;
|
|
653
|
+
if (value.length <= visible) {
|
|
654
|
+
masked[rule.field] = maskChar.repeat(value.length);
|
|
655
|
+
} else {
|
|
656
|
+
masked[rule.field] = value.slice(0, visible) + maskChar.repeat(value.length - visible);
|
|
657
|
+
}
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case "hash":
|
|
661
|
+
masked[rule.field] = `[HASHED:${simpleHash(value)}]`;
|
|
662
|
+
break;
|
|
663
|
+
case "redact":
|
|
664
|
+
masked[rule.field] = "[REDACTED]";
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return masked;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Update the security policy.
|
|
672
|
+
*/
|
|
673
|
+
updatePolicy(policy) {
|
|
674
|
+
this.policy = { ...this.policy, ...policy };
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Get current security policy.
|
|
678
|
+
*/
|
|
679
|
+
getPolicy() {
|
|
680
|
+
return { ...this.policy };
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
function simpleHash(str) {
|
|
684
|
+
let hash = 0;
|
|
685
|
+
for (let i = 0; i < str.length; i++) {
|
|
686
|
+
const char = str.charCodeAt(i);
|
|
687
|
+
hash = (hash << 5) - hash + char;
|
|
688
|
+
hash = hash & hash;
|
|
689
|
+
}
|
|
690
|
+
return Math.abs(hash).toString(36);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/studio.ts
|
|
694
|
+
function createDefaultCanvasConfig(overrides) {
|
|
695
|
+
return {
|
|
696
|
+
width: 1200,
|
|
697
|
+
height: 800,
|
|
698
|
+
background: "grid",
|
|
699
|
+
gridSize: 8,
|
|
700
|
+
snapToGrid: true,
|
|
701
|
+
zoom: {
|
|
702
|
+
min: 0.25,
|
|
703
|
+
max: 3,
|
|
704
|
+
step: 0.1,
|
|
705
|
+
current: 1
|
|
706
|
+
},
|
|
707
|
+
panOffset: { x: 0, y: 0 },
|
|
708
|
+
showMinimap: false,
|
|
709
|
+
minimapPosition: "bottom-right",
|
|
710
|
+
...overrides
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function snapToGrid(x, y, gridSize) {
|
|
714
|
+
return {
|
|
715
|
+
x: Math.round(x / gridSize) * gridSize,
|
|
716
|
+
y: Math.round(y / gridSize) * gridSize
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function calculateAutoLayout(items, canvasWidth, padding = 40, gap = 40) {
|
|
720
|
+
const positions = [];
|
|
721
|
+
let currentX = padding;
|
|
722
|
+
let currentY = padding;
|
|
723
|
+
let rowMaxHeight = 0;
|
|
724
|
+
for (const item of items) {
|
|
725
|
+
if (currentX + item.width + padding > canvasWidth) {
|
|
726
|
+
currentX = padding;
|
|
727
|
+
currentY += rowMaxHeight + gap;
|
|
728
|
+
rowMaxHeight = 0;
|
|
729
|
+
}
|
|
730
|
+
positions.push({ id: item.id, x: currentX, y: currentY });
|
|
731
|
+
currentX += item.width + gap;
|
|
732
|
+
rowMaxHeight = Math.max(rowMaxHeight, item.height);
|
|
733
|
+
}
|
|
734
|
+
return positions;
|
|
735
|
+
}
|
|
736
|
+
|
|
367
737
|
// src/index.ts
|
|
368
738
|
var ObjectStackAdapter = class {
|
|
369
739
|
constructor(config) {
|
|
@@ -377,11 +747,15 @@ var ObjectStackAdapter = class {
|
|
|
377
747
|
__publicField(this, "maxReconnectAttempts");
|
|
378
748
|
__publicField(this, "reconnectDelay");
|
|
379
749
|
__publicField(this, "reconnectAttempts", 0);
|
|
750
|
+
__publicField(this, "baseUrl");
|
|
751
|
+
__publicField(this, "token");
|
|
380
752
|
this.client = new import_client.ObjectStackClient(config);
|
|
381
753
|
this.metadataCache = new MetadataCache(config.cache);
|
|
382
754
|
this.autoReconnect = config.autoReconnect ?? true;
|
|
383
755
|
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 3;
|
|
384
756
|
this.reconnectDelay = config.reconnectDelay ?? 1e3;
|
|
757
|
+
this.baseUrl = config.baseUrl;
|
|
758
|
+
this.token = config.token;
|
|
385
759
|
}
|
|
386
760
|
/**
|
|
387
761
|
* Ensure the client is connected to the server.
|
|
@@ -506,13 +880,15 @@ var ObjectStackAdapter = class {
|
|
|
506
880
|
};
|
|
507
881
|
}
|
|
508
882
|
const resultObj = result;
|
|
883
|
+
const records = resultObj.records || resultObj.value || [];
|
|
884
|
+
const total = resultObj.total ?? resultObj.count ?? records.length;
|
|
509
885
|
return {
|
|
510
|
-
data:
|
|
511
|
-
total
|
|
886
|
+
data: records,
|
|
887
|
+
total,
|
|
512
888
|
// Calculate page number safely
|
|
513
889
|
page: params?.$skip && params.$top ? Math.floor(params.$skip / params.$top) + 1 : 1,
|
|
514
890
|
pageSize: params?.$top,
|
|
515
|
-
hasMore: params?.$top ?
|
|
891
|
+
hasMore: params?.$top ? records.length === params.$top : false
|
|
516
892
|
};
|
|
517
893
|
}
|
|
518
894
|
/**
|
|
@@ -521,8 +897,8 @@ var ObjectStackAdapter = class {
|
|
|
521
897
|
async findOne(resource, id, _params) {
|
|
522
898
|
await this.connect();
|
|
523
899
|
try {
|
|
524
|
-
const
|
|
525
|
-
return record;
|
|
900
|
+
const result = await this.client.data.get(resource, String(id));
|
|
901
|
+
return result.record;
|
|
526
902
|
} catch (error) {
|
|
527
903
|
if (error?.status === 404) {
|
|
528
904
|
return null;
|
|
@@ -535,14 +911,16 @@ var ObjectStackAdapter = class {
|
|
|
535
911
|
*/
|
|
536
912
|
async create(resource, data) {
|
|
537
913
|
await this.connect();
|
|
538
|
-
|
|
914
|
+
const result = await this.client.data.create(resource, data);
|
|
915
|
+
return result.record;
|
|
539
916
|
}
|
|
540
917
|
/**
|
|
541
918
|
* Update an existing record.
|
|
542
919
|
*/
|
|
543
920
|
async update(resource, id, data) {
|
|
544
921
|
await this.connect();
|
|
545
|
-
|
|
922
|
+
const result = await this.client.data.update(resource, String(id), data);
|
|
923
|
+
return result.record;
|
|
546
924
|
}
|
|
547
925
|
/**
|
|
548
926
|
* Delete a record.
|
|
@@ -550,7 +928,7 @@ var ObjectStackAdapter = class {
|
|
|
550
928
|
async delete(resource, id) {
|
|
551
929
|
await this.connect();
|
|
552
930
|
const result = await this.client.data.delete(resource, String(id));
|
|
553
|
-
return result.
|
|
931
|
+
return result.deleted;
|
|
554
932
|
}
|
|
555
933
|
/**
|
|
556
934
|
* Bulk operations with optimized batch processing and error handling.
|
|
@@ -632,7 +1010,7 @@ var ObjectStackAdapter = class {
|
|
|
632
1010
|
}
|
|
633
1011
|
try {
|
|
634
1012
|
const result = await this.client.data.update(resource, String(id), item);
|
|
635
|
-
results.push(result);
|
|
1013
|
+
results.push(result.record);
|
|
636
1014
|
completed++;
|
|
637
1015
|
emitProgress();
|
|
638
1016
|
} catch (error) {
|
|
@@ -733,7 +1111,7 @@ var ObjectStackAdapter = class {
|
|
|
733
1111
|
await this.connect();
|
|
734
1112
|
try {
|
|
735
1113
|
const schema = await this.metadataCache.get(objectName, async () => {
|
|
736
|
-
const result = await this.client.meta.
|
|
1114
|
+
const result = await this.client.meta.getItem("object", objectName);
|
|
737
1115
|
if (result && result.item) {
|
|
738
1116
|
return result.item;
|
|
739
1117
|
}
|
|
@@ -757,6 +1135,111 @@ var ObjectStackAdapter = class {
|
|
|
757
1135
|
getClient() {
|
|
758
1136
|
return this.client;
|
|
759
1137
|
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Get the discovery information from the connected server.
|
|
1140
|
+
* Returns the capabilities and service status of the ObjectStack server.
|
|
1141
|
+
*
|
|
1142
|
+
* Note: This accesses an internal property of the ObjectStackClient.
|
|
1143
|
+
* The discovery data is populated during client.connect() and cached.
|
|
1144
|
+
*
|
|
1145
|
+
* @returns Promise resolving to discovery data, or null if not connected
|
|
1146
|
+
*/
|
|
1147
|
+
async getDiscovery() {
|
|
1148
|
+
try {
|
|
1149
|
+
await this.connect();
|
|
1150
|
+
return this.client.discoveryInfo || null;
|
|
1151
|
+
} catch {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Get a view definition for an object.
|
|
1157
|
+
* Attempts to fetch from the server metadata API.
|
|
1158
|
+
* Falls back to null if the server doesn't provide view definitions,
|
|
1159
|
+
* allowing the consumer to use static config.
|
|
1160
|
+
*
|
|
1161
|
+
* @param objectName - Object name
|
|
1162
|
+
* @param viewId - View identifier
|
|
1163
|
+
* @returns Promise resolving to the view definition or null
|
|
1164
|
+
*/
|
|
1165
|
+
async getView(objectName, viewId) {
|
|
1166
|
+
await this.connect();
|
|
1167
|
+
try {
|
|
1168
|
+
const cacheKey = `view:${objectName}:${viewId}`;
|
|
1169
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1170
|
+
const result = await this.client.meta.getItem(objectName, `views/${viewId}`);
|
|
1171
|
+
if (result && result.item) return result.item;
|
|
1172
|
+
return result ?? null;
|
|
1173
|
+
});
|
|
1174
|
+
} catch {
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Get an application definition by name or ID.
|
|
1180
|
+
* Attempts to fetch from the server metadata API.
|
|
1181
|
+
* Falls back to null if the server doesn't provide app definitions,
|
|
1182
|
+
* allowing the consumer to use static config.
|
|
1183
|
+
*
|
|
1184
|
+
* @param appId - Application identifier
|
|
1185
|
+
* @returns Promise resolving to the app definition or null
|
|
1186
|
+
*/
|
|
1187
|
+
async getApp(appId) {
|
|
1188
|
+
await this.connect();
|
|
1189
|
+
try {
|
|
1190
|
+
const cacheKey = `app:${appId}`;
|
|
1191
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1192
|
+
const result = await this.client.meta.getItem("apps", appId);
|
|
1193
|
+
if (result && result.item) return result.item;
|
|
1194
|
+
return result ?? null;
|
|
1195
|
+
});
|
|
1196
|
+
} catch {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Get a page definition from ObjectStack.
|
|
1202
|
+
* Uses the metadata API to fetch page layouts.
|
|
1203
|
+
* Returns null if the server doesn't support page metadata.
|
|
1204
|
+
*/
|
|
1205
|
+
async getPage(pageId) {
|
|
1206
|
+
await this.connect();
|
|
1207
|
+
try {
|
|
1208
|
+
const cacheKey = `page:${pageId}`;
|
|
1209
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1210
|
+
const result = await this.client.meta.getItem("pages", pageId);
|
|
1211
|
+
if (result && result.item) return result.item;
|
|
1212
|
+
return result ?? null;
|
|
1213
|
+
});
|
|
1214
|
+
} catch {
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Get multiple metadata items from ObjectStack.
|
|
1220
|
+
* Uses v3.0.0 metadata API pattern: getItems for batch retrieval.
|
|
1221
|
+
*/
|
|
1222
|
+
async getItems(category, names) {
|
|
1223
|
+
await this.connect();
|
|
1224
|
+
const results = await Promise.all(
|
|
1225
|
+
names.map(async (name) => {
|
|
1226
|
+
const cacheKey = `${category}:${name}`;
|
|
1227
|
+
return this.metadataCache.get(cacheKey, async () => {
|
|
1228
|
+
const result = await this.client.meta.getItem(category, name);
|
|
1229
|
+
if (result && result.item) return result.item;
|
|
1230
|
+
return result;
|
|
1231
|
+
});
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1234
|
+
return results;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Get cached metadata if available, without triggering a fetch.
|
|
1238
|
+
* Uses v3.0.0 metadata API pattern: getCached for synchronous cache access.
|
|
1239
|
+
*/
|
|
1240
|
+
getCached(key) {
|
|
1241
|
+
return this.metadataCache.getCachedSync(key);
|
|
1242
|
+
}
|
|
760
1243
|
/**
|
|
761
1244
|
* Get cache statistics for monitoring performance.
|
|
762
1245
|
*/
|
|
@@ -777,6 +1260,98 @@ var ObjectStackAdapter = class {
|
|
|
777
1260
|
clearCache() {
|
|
778
1261
|
this.metadataCache.clear();
|
|
779
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Upload a single file to a resource.
|
|
1265
|
+
* Posts the file as multipart/form-data to the ObjectStack server.
|
|
1266
|
+
*
|
|
1267
|
+
* @param resource - The resource/object name to attach the file to
|
|
1268
|
+
* @param file - File object or Blob to upload
|
|
1269
|
+
* @param options - Additional upload options (recordId, fieldName, metadata)
|
|
1270
|
+
* @returns Promise resolving to the upload result (file URL, metadata)
|
|
1271
|
+
*/
|
|
1272
|
+
async uploadFile(resource, file, options) {
|
|
1273
|
+
await this.connect();
|
|
1274
|
+
const formData = new FormData();
|
|
1275
|
+
formData.append("file", file);
|
|
1276
|
+
if (options?.recordId) {
|
|
1277
|
+
formData.append("recordId", options.recordId);
|
|
1278
|
+
}
|
|
1279
|
+
if (options?.fieldName) {
|
|
1280
|
+
formData.append("fieldName", options.fieldName);
|
|
1281
|
+
}
|
|
1282
|
+
if (options?.metadata) {
|
|
1283
|
+
formData.append("metadata", JSON.stringify(options.metadata));
|
|
1284
|
+
}
|
|
1285
|
+
const url = `${this.baseUrl}/api/data/${encodeURIComponent(resource)}/upload`;
|
|
1286
|
+
const response = await fetch(url, {
|
|
1287
|
+
method: "POST",
|
|
1288
|
+
body: formData,
|
|
1289
|
+
headers: {
|
|
1290
|
+
...this.getAuthHeaders()
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
if (!response.ok) {
|
|
1294
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
1295
|
+
throw new ObjectStackError(
|
|
1296
|
+
error.message || `Upload failed with status ${response.status}`,
|
|
1297
|
+
"UPLOAD_ERROR",
|
|
1298
|
+
response.status
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
return response.json();
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Upload multiple files to a resource.
|
|
1305
|
+
* Posts all files as a single multipart/form-data request.
|
|
1306
|
+
*
|
|
1307
|
+
* @param resource - The resource/object name to attach the files to
|
|
1308
|
+
* @param files - Array of File objects or Blobs to upload
|
|
1309
|
+
* @param options - Additional upload options
|
|
1310
|
+
* @returns Promise resolving to array of upload results
|
|
1311
|
+
*/
|
|
1312
|
+
async uploadFiles(resource, files, options) {
|
|
1313
|
+
await this.connect();
|
|
1314
|
+
const formData = new FormData();
|
|
1315
|
+
files.forEach((file, idx) => {
|
|
1316
|
+
formData.append(`files`, file, file.name || `file-${idx}`);
|
|
1317
|
+
});
|
|
1318
|
+
if (options?.recordId) {
|
|
1319
|
+
formData.append("recordId", options.recordId);
|
|
1320
|
+
}
|
|
1321
|
+
if (options?.fieldName) {
|
|
1322
|
+
formData.append("fieldName", options.fieldName);
|
|
1323
|
+
}
|
|
1324
|
+
if (options?.metadata) {
|
|
1325
|
+
formData.append("metadata", JSON.stringify(options.metadata));
|
|
1326
|
+
}
|
|
1327
|
+
const url = `${this.baseUrl}/api/data/${encodeURIComponent(resource)}/upload`;
|
|
1328
|
+
const response = await fetch(url, {
|
|
1329
|
+
method: "POST",
|
|
1330
|
+
body: formData,
|
|
1331
|
+
headers: {
|
|
1332
|
+
...this.getAuthHeaders()
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
if (!response.ok) {
|
|
1336
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
1337
|
+
throw new ObjectStackError(
|
|
1338
|
+
error.message || `Upload failed with status ${response.status}`,
|
|
1339
|
+
"UPLOAD_ERROR",
|
|
1340
|
+
response.status
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
return response.json();
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Get authorization headers from the adapter config.
|
|
1347
|
+
*/
|
|
1348
|
+
getAuthHeaders() {
|
|
1349
|
+
const headers = {};
|
|
1350
|
+
if (this.token) {
|
|
1351
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
1352
|
+
}
|
|
1353
|
+
return headers;
|
|
1354
|
+
}
|
|
780
1355
|
};
|
|
781
1356
|
function createObjectStackAdapter(config) {
|
|
782
1357
|
return new ObjectStackAdapter(config);
|
|
@@ -785,13 +1360,21 @@ function createObjectStackAdapter(config) {
|
|
|
785
1360
|
0 && (module.exports = {
|
|
786
1361
|
AuthenticationError,
|
|
787
1362
|
BulkOperationError,
|
|
1363
|
+
CloudOperations,
|
|
788
1364
|
ConnectionError,
|
|
1365
|
+
IntegrationManager,
|
|
789
1366
|
MetadataNotFoundError,
|
|
790
1367
|
ObjectStackAdapter,
|
|
791
1368
|
ObjectStackError,
|
|
1369
|
+
SecurityManager,
|
|
792
1370
|
ValidationError,
|
|
1371
|
+
calculateAutoLayout,
|
|
1372
|
+
createDefaultCanvasConfig,
|
|
793
1373
|
createErrorFromResponse,
|
|
794
1374
|
createObjectStackAdapter,
|
|
1375
|
+
generateContractManifest,
|
|
795
1376
|
isErrorType,
|
|
796
|
-
isObjectStackError
|
|
1377
|
+
isObjectStackError,
|
|
1378
|
+
snapToGrid,
|
|
1379
|
+
validatePluginContract
|
|
797
1380
|
});
|