@object-ui/data-objectstack 4.0.7 → 4.0.8
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/CHANGELOG.md +7 -0
- package/dist/index.cjs +150 -10
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +149 -10
- package/package.json +5 -5
- package/src/index.ts +209 -11
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
createObjectStackAdapter: () => createObjectStackAdapter,
|
|
40
40
|
generateContractManifest: () => generateContractManifest,
|
|
41
41
|
getSharedDiscovery: () => getSharedDiscovery,
|
|
42
|
+
is404Error: () => is404Error,
|
|
42
43
|
isErrorType: () => isErrorType,
|
|
43
44
|
isObjectStackError: () => isObjectStackError,
|
|
44
45
|
snapToGrid: () => snapToGrid,
|
|
@@ -895,6 +896,44 @@ async function getSharedDiscovery(baseUrl, fetcher) {
|
|
|
895
896
|
function clearSharedDiscoveryCache() {
|
|
896
897
|
discoveryCache.clear();
|
|
897
898
|
}
|
|
899
|
+
function is404Error(error) {
|
|
900
|
+
if (!error || typeof error !== "object") return false;
|
|
901
|
+
const err = error;
|
|
902
|
+
if (err.httpStatus === 404 || err.status === 404 || err.statusCode === 404) {
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
const code = typeof err.code === "string" ? err.code : "";
|
|
906
|
+
return code === "object_not_found" || code === "record_not_found";
|
|
907
|
+
}
|
|
908
|
+
function createQuietHttpLogger() {
|
|
909
|
+
const isExpected404 = (meta) => {
|
|
910
|
+
if (!meta || typeof meta !== "object") return false;
|
|
911
|
+
if (meta.status === 404 || meta.statusCode === 404) return true;
|
|
912
|
+
const errBody = meta.error;
|
|
913
|
+
if (errBody && typeof errBody === "object") {
|
|
914
|
+
const code = errBody.code;
|
|
915
|
+
if (code === "object_not_found" || code === "record_not_found") return true;
|
|
916
|
+
}
|
|
917
|
+
return false;
|
|
918
|
+
};
|
|
919
|
+
const logger = {
|
|
920
|
+
debug: (message, meta) => console.debug(message, meta ?? ""),
|
|
921
|
+
info: (message, meta) => console.info(message, meta ?? ""),
|
|
922
|
+
warn: (message, meta) => console.warn(message, meta ?? ""),
|
|
923
|
+
error: (message, error, meta) => {
|
|
924
|
+
if (isExpected404(meta)) {
|
|
925
|
+
console.debug(`[ObjectStack] ${message} (suppressed expected 404)`, meta);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
console.error(message, error ?? "", meta ?? "");
|
|
929
|
+
},
|
|
930
|
+
fatal: (message, error, meta) => console.error(message, error ?? "", meta ?? ""),
|
|
931
|
+
log: (message, ...args) => console.log(message, ...args),
|
|
932
|
+
child: () => logger,
|
|
933
|
+
withTrace: () => logger
|
|
934
|
+
};
|
|
935
|
+
return logger;
|
|
936
|
+
}
|
|
898
937
|
function stableStringify(value) {
|
|
899
938
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
900
939
|
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
@@ -923,7 +962,7 @@ var ObjectStackAdapter = class {
|
|
|
923
962
|
// multiple sibling components requesting the same dataset on first paint)
|
|
924
963
|
// into a single network round trip.
|
|
925
964
|
__publicField(this, "inflightFinds", /* @__PURE__ */ new Map());
|
|
926
|
-
this.client = new import_client.ObjectStackClient(config);
|
|
965
|
+
this.client = new import_client.ObjectStackClient({ ...config, logger: createQuietHttpLogger() });
|
|
927
966
|
this.metadataCache = new MetadataCache(config.cache);
|
|
928
967
|
this.autoReconnect = config.autoReconnect ?? true;
|
|
929
968
|
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 3;
|
|
@@ -1074,11 +1113,12 @@ var ObjectStackAdapter = class {
|
|
|
1074
1113
|
return this.normalizeQueryResult(result, params);
|
|
1075
1114
|
})();
|
|
1076
1115
|
this.inflightFinds.set(key, promise);
|
|
1077
|
-
|
|
1116
|
+
const cleanup = () => {
|
|
1078
1117
|
if (this.inflightFinds.get(key) === promise) {
|
|
1079
1118
|
this.inflightFinds.delete(key);
|
|
1080
1119
|
}
|
|
1081
|
-
}
|
|
1120
|
+
};
|
|
1121
|
+
promise.then(cleanup, cleanup);
|
|
1082
1122
|
return promise;
|
|
1083
1123
|
}
|
|
1084
1124
|
/**
|
|
@@ -1101,7 +1141,7 @@ var ObjectStackAdapter = class {
|
|
|
1101
1141
|
const records = resultObj.records || resultObj.value || [];
|
|
1102
1142
|
return records[0] || null;
|
|
1103
1143
|
} catch (error) {
|
|
1104
|
-
if (error
|
|
1144
|
+
if (is404Error(error)) {
|
|
1105
1145
|
return null;
|
|
1106
1146
|
}
|
|
1107
1147
|
}
|
|
@@ -1110,7 +1150,7 @@ var ObjectStackAdapter = class {
|
|
|
1110
1150
|
const result = await this.client.data.get(resource, String(id));
|
|
1111
1151
|
return result.record;
|
|
1112
1152
|
} catch (error) {
|
|
1113
|
-
if (error
|
|
1153
|
+
if (is404Error(error)) {
|
|
1114
1154
|
return null;
|
|
1115
1155
|
}
|
|
1116
1156
|
throw error;
|
|
@@ -1434,7 +1474,7 @@ var ObjectStackAdapter = class {
|
|
|
1434
1474
|
return schema;
|
|
1435
1475
|
} catch (error) {
|
|
1436
1476
|
const errorObj = error;
|
|
1437
|
-
if (errorObj
|
|
1477
|
+
if (is404Error(errorObj)) {
|
|
1438
1478
|
throw new MetadataNotFoundError(objectName, { originalError: error });
|
|
1439
1479
|
}
|
|
1440
1480
|
if (error instanceof ObjectStackError) {
|
|
@@ -1466,6 +1506,43 @@ var ObjectStackAdapter = class {
|
|
|
1466
1506
|
return null;
|
|
1467
1507
|
}
|
|
1468
1508
|
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Batch-fetch all persisted view overrides for an object.
|
|
1511
|
+
*
|
|
1512
|
+
* Per-view runtime overrides (density, column widths, sort, …) are
|
|
1513
|
+
* stored in the metadata registry under key `<objectName>/<viewName>`.
|
|
1514
|
+
* Loading them per-view fires N HTTP GETs that return 404 for views
|
|
1515
|
+
* the user has never customized — generates console noise on every
|
|
1516
|
+
* page load. This batch method performs a single
|
|
1517
|
+
* `GET /api/v1/meta/<objectName>` (returns `{type, items}`) and
|
|
1518
|
+
* returns a `{viewName: override}` map. Returns an empty map if the
|
|
1519
|
+
* server doesn't expose the listing or returns no items.
|
|
1520
|
+
*
|
|
1521
|
+
* Result is cached identically to {@link getView}; saving a view via
|
|
1522
|
+
* {@link updateViewConfig} invalidates the cache.
|
|
1523
|
+
*
|
|
1524
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
1525
|
+
* @returns Map keyed by view name with the persisted override config
|
|
1526
|
+
*/
|
|
1527
|
+
async listViewOverrides(objectName) {
|
|
1528
|
+
await this.connect();
|
|
1529
|
+
try {
|
|
1530
|
+
const cacheKey = `view-overrides:${objectName}`;
|
|
1531
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1532
|
+
const result = await this.client.meta.getItems(objectName);
|
|
1533
|
+
const items = Array.isArray(result?.items) ? result.items : [];
|
|
1534
|
+
const out = {};
|
|
1535
|
+
for (const it of items) {
|
|
1536
|
+
if (!it || typeof it !== "object") continue;
|
|
1537
|
+
const key = it.name ?? it.id ?? it._name;
|
|
1538
|
+
if (typeof key === "string" && key) out[key] = it;
|
|
1539
|
+
}
|
|
1540
|
+
return out;
|
|
1541
|
+
});
|
|
1542
|
+
} catch {
|
|
1543
|
+
return {};
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1469
1546
|
/**
|
|
1470
1547
|
* Get a view definition for an object.
|
|
1471
1548
|
* Attempts to fetch from the server metadata API.
|
|
@@ -1481,7 +1558,7 @@ var ObjectStackAdapter = class {
|
|
|
1481
1558
|
try {
|
|
1482
1559
|
const cacheKey = `view:${objectName}:${viewId}`;
|
|
1483
1560
|
return await this.metadataCache.get(cacheKey, async () => {
|
|
1484
|
-
const result = await this.client.meta.getItem(objectName,
|
|
1561
|
+
const result = await this.client.meta.getItem(objectName, viewId);
|
|
1485
1562
|
if (result && result.item) return result.item;
|
|
1486
1563
|
return result ?? null;
|
|
1487
1564
|
});
|
|
@@ -1489,6 +1566,40 @@ var ObjectStackAdapter = class {
|
|
|
1489
1566
|
return null;
|
|
1490
1567
|
}
|
|
1491
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Persist a view definition for an object.
|
|
1571
|
+
*
|
|
1572
|
+
* Symmetric counterpart to {@link getView}: writes the view to the
|
|
1573
|
+
* server metadata store via `client.meta.saveItem`, then invalidates
|
|
1574
|
+
* the matching cache entry so the next {@link getView} reflects the
|
|
1575
|
+
* new payload. Returns the persisted item when the server echoes it,
|
|
1576
|
+
* otherwise undefined.
|
|
1577
|
+
*
|
|
1578
|
+
* Used by ObjectView for "live" toolbar persistence (density,
|
|
1579
|
+
* column widths, sort, etc.) and by the View Config Panel for
|
|
1580
|
+
* explicit saves.
|
|
1581
|
+
*
|
|
1582
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
1583
|
+
* @param viewId - View identifier (e.g. 'all_leads')
|
|
1584
|
+
* @param config - Full view definition to persist
|
|
1585
|
+
*/
|
|
1586
|
+
async updateViewConfig(objectName, viewId, config) {
|
|
1587
|
+
await this.connect();
|
|
1588
|
+
try {
|
|
1589
|
+
const result = await this.client.meta.saveItem(
|
|
1590
|
+
objectName,
|
|
1591
|
+
viewId,
|
|
1592
|
+
config
|
|
1593
|
+
);
|
|
1594
|
+
const cacheKey = `view:${objectName}:${viewId}`;
|
|
1595
|
+
this.metadataCache.invalidate?.(cacheKey);
|
|
1596
|
+
this.metadataCache.invalidate?.(`view-overrides:${objectName}`);
|
|
1597
|
+
if (result && result.item) return result.item;
|
|
1598
|
+
return result ?? void 0;
|
|
1599
|
+
} catch (err) {
|
|
1600
|
+
throw err;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1492
1603
|
/**
|
|
1493
1604
|
* Get an application definition by name or ID.
|
|
1494
1605
|
* Attempts to fetch from the server metadata API.
|
|
@@ -1529,6 +1640,34 @@ var ObjectStackAdapter = class {
|
|
|
1529
1640
|
return null;
|
|
1530
1641
|
}
|
|
1531
1642
|
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Update (upsert) a dashboard definition.
|
|
1645
|
+
*
|
|
1646
|
+
* Dashboards are control-plane metadata, not data records. Persist via
|
|
1647
|
+
* `client.meta.saveItem('dashboard', name, schema)` which routes to
|
|
1648
|
+
* `PUT /api/v1/meta/dashboard/:name`. After save, invalidates the
|
|
1649
|
+
* relevant metadata cache entry so the next dashboard read reflects
|
|
1650
|
+
* the new payload.
|
|
1651
|
+
*
|
|
1652
|
+
* @param dashboardName - Dashboard identifier (e.g. 'crm_overview_dashboard')
|
|
1653
|
+
* @param schema - Full dashboard schema (widgets, layout, etc.)
|
|
1654
|
+
*/
|
|
1655
|
+
async updateDashboard(dashboardName, schema) {
|
|
1656
|
+
await this.connect();
|
|
1657
|
+
try {
|
|
1658
|
+
const result = await this.client.meta.saveItem(
|
|
1659
|
+
"dashboard",
|
|
1660
|
+
dashboardName,
|
|
1661
|
+
schema
|
|
1662
|
+
);
|
|
1663
|
+
this.metadataCache.invalidate?.("dashboards");
|
|
1664
|
+
this.metadataCache.invalidate?.(`dashboard:${dashboardName}`);
|
|
1665
|
+
if (result && result.item) return result.item;
|
|
1666
|
+
return result ?? void 0;
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
throw err;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1532
1671
|
/**
|
|
1533
1672
|
* Perform server-side aggregation via the ObjectStack analytics API.
|
|
1534
1673
|
* Uses `this.client.analytics.query()` from @objectstack/client to leverage
|
|
@@ -1547,7 +1686,7 @@ var ObjectStackAdapter = class {
|
|
|
1547
1686
|
dimensions: params.groupBy && params.groupBy !== "_all" ? [params.groupBy] : []
|
|
1548
1687
|
};
|
|
1549
1688
|
if (params.filter) {
|
|
1550
|
-
payload.
|
|
1689
|
+
payload.where = params.filter;
|
|
1551
1690
|
}
|
|
1552
1691
|
const data = await this.client.analytics.query(payload);
|
|
1553
1692
|
const rawRows = Array.isArray(data) ? data : data?.rows && Array.isArray(data.rows) ? data.rows : data?.data && Array.isArray(data.data) ? data.data : data?.data?.rows && Array.isArray(data.data.rows) ? data.data.rows : data?.results && Array.isArray(data.results) ? data.results : [];
|
|
@@ -1558,7 +1697,7 @@ var ObjectStackAdapter = class {
|
|
|
1558
1697
|
return true;
|
|
1559
1698
|
});
|
|
1560
1699
|
if (measureMissing) {
|
|
1561
|
-
const result = await this.find(resource);
|
|
1700
|
+
const result = await this.find(resource, params.filter ? { $filter: params.filter } : void 0);
|
|
1562
1701
|
const records = result.data || [];
|
|
1563
1702
|
if (records.length === 0) return [];
|
|
1564
1703
|
return this.aggregateClientSide(records, params);
|
|
@@ -1572,7 +1711,7 @@ var ObjectStackAdapter = class {
|
|
|
1572
1711
|
return mapped;
|
|
1573
1712
|
});
|
|
1574
1713
|
} catch {
|
|
1575
|
-
const result = await this.find(resource);
|
|
1714
|
+
const result = await this.find(resource, params.filter ? { $filter: params.filter } : void 0);
|
|
1576
1715
|
const records = result.data || [];
|
|
1577
1716
|
if (records.length === 0) return [];
|
|
1578
1717
|
return this.aggregateClientSide(records, params);
|
|
@@ -1771,6 +1910,7 @@ function createObjectStackAdapter(config) {
|
|
|
1771
1910
|
createObjectStackAdapter,
|
|
1772
1911
|
generateContractManifest,
|
|
1773
1912
|
getSharedDiscovery,
|
|
1913
|
+
is404Error,
|
|
1774
1914
|
isErrorType,
|
|
1775
1915
|
isObjectStackError,
|
|
1776
1916
|
snapToGrid,
|
package/dist/index.d.cts
CHANGED
|
@@ -648,6 +648,16 @@ declare function calculateAutoLayout(items: Array<{
|
|
|
648
648
|
declare function getSharedDiscovery(baseUrl: string, fetcher: () => Promise<unknown>): Promise<unknown>;
|
|
649
649
|
/** Test/dev helper to drop the cache (e.g. on logout or origin change). */
|
|
650
650
|
declare function clearSharedDiscoveryCache(): void;
|
|
651
|
+
/**
|
|
652
|
+
* Detect "missing resource" errors regardless of where they originate.
|
|
653
|
+
*
|
|
654
|
+
* The ObjectStack client decorates thrown errors with `httpStatus` (and a
|
|
655
|
+
* machine-readable `code` such as `object_not_found`/`record_not_found`),
|
|
656
|
+
* while raw `fetch()` callers may surface `status` or `statusCode`. Treat
|
|
657
|
+
* any of these as a 404 so callers can degrade gracefully instead of
|
|
658
|
+
* tripping on the property-name mismatch.
|
|
659
|
+
*/
|
|
660
|
+
declare function is404Error(error: unknown): boolean;
|
|
651
661
|
/**
|
|
652
662
|
* Connection state for monitoring
|
|
653
663
|
*/
|
|
@@ -838,6 +848,25 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
838
848
|
* @returns Promise resolving to discovery data, or null if not connected
|
|
839
849
|
*/
|
|
840
850
|
getDiscovery(): Promise<unknown | null>;
|
|
851
|
+
/**
|
|
852
|
+
* Batch-fetch all persisted view overrides for an object.
|
|
853
|
+
*
|
|
854
|
+
* Per-view runtime overrides (density, column widths, sort, …) are
|
|
855
|
+
* stored in the metadata registry under key `<objectName>/<viewName>`.
|
|
856
|
+
* Loading them per-view fires N HTTP GETs that return 404 for views
|
|
857
|
+
* the user has never customized — generates console noise on every
|
|
858
|
+
* page load. This batch method performs a single
|
|
859
|
+
* `GET /api/v1/meta/<objectName>` (returns `{type, items}`) and
|
|
860
|
+
* returns a `{viewName: override}` map. Returns an empty map if the
|
|
861
|
+
* server doesn't expose the listing or returns no items.
|
|
862
|
+
*
|
|
863
|
+
* Result is cached identically to {@link getView}; saving a view via
|
|
864
|
+
* {@link updateViewConfig} invalidates the cache.
|
|
865
|
+
*
|
|
866
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
867
|
+
* @returns Map keyed by view name with the persisted override config
|
|
868
|
+
*/
|
|
869
|
+
listViewOverrides(objectName: string): Promise<Record<string, any>>;
|
|
841
870
|
/**
|
|
842
871
|
* Get a view definition for an object.
|
|
843
872
|
* Attempts to fetch from the server metadata API.
|
|
@@ -849,6 +878,24 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
849
878
|
* @returns Promise resolving to the view definition or null
|
|
850
879
|
*/
|
|
851
880
|
getView(objectName: string, viewId: string): Promise<unknown | null>;
|
|
881
|
+
/**
|
|
882
|
+
* Persist a view definition for an object.
|
|
883
|
+
*
|
|
884
|
+
* Symmetric counterpart to {@link getView}: writes the view to the
|
|
885
|
+
* server metadata store via `client.meta.saveItem`, then invalidates
|
|
886
|
+
* the matching cache entry so the next {@link getView} reflects the
|
|
887
|
+
* new payload. Returns the persisted item when the server echoes it,
|
|
888
|
+
* otherwise undefined.
|
|
889
|
+
*
|
|
890
|
+
* Used by ObjectView for "live" toolbar persistence (density,
|
|
891
|
+
* column widths, sort, etc.) and by the View Config Panel for
|
|
892
|
+
* explicit saves.
|
|
893
|
+
*
|
|
894
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
895
|
+
* @param viewId - View identifier (e.g. 'all_leads')
|
|
896
|
+
* @param config - Full view definition to persist
|
|
897
|
+
*/
|
|
898
|
+
updateViewConfig(objectName: string, viewId: string, config: Record<string, any>): Promise<Record<string, any> | void>;
|
|
852
899
|
/**
|
|
853
900
|
* Get an application definition by name or ID.
|
|
854
901
|
* Attempts to fetch from the server metadata API.
|
|
@@ -865,6 +912,19 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
865
912
|
* Returns null if the server doesn't support page metadata.
|
|
866
913
|
*/
|
|
867
914
|
getPage(pageId: string): Promise<unknown | null>;
|
|
915
|
+
/**
|
|
916
|
+
* Update (upsert) a dashboard definition.
|
|
917
|
+
*
|
|
918
|
+
* Dashboards are control-plane metadata, not data records. Persist via
|
|
919
|
+
* `client.meta.saveItem('dashboard', name, schema)` which routes to
|
|
920
|
+
* `PUT /api/v1/meta/dashboard/:name`. After save, invalidates the
|
|
921
|
+
* relevant metadata cache entry so the next dashboard read reflects
|
|
922
|
+
* the new payload.
|
|
923
|
+
*
|
|
924
|
+
* @param dashboardName - Dashboard identifier (e.g. 'crm_overview_dashboard')
|
|
925
|
+
* @param schema - Full dashboard schema (widgets, layout, etc.)
|
|
926
|
+
*/
|
|
927
|
+
updateDashboard(dashboardName: string, schema: Record<string, any>): Promise<Record<string, any> | void>;
|
|
868
928
|
/**
|
|
869
929
|
* Perform server-side aggregation via the ObjectStack analytics API.
|
|
870
930
|
* Uses `this.client.analytics.query()` from @objectstack/client to leverage
|
|
@@ -966,4 +1026,4 @@ declare function createObjectStackAdapter<T = unknown>(config: {
|
|
|
966
1026
|
reconnectDelay?: number;
|
|
967
1027
|
}): DataSource<T>;
|
|
968
1028
|
|
|
969
|
-
export { type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CacheStats, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, type DataMaskingConfig, type DataMaskingRule, type EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, MetadataNotFoundError, ObjectStackAdapter, ObjectStackError, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, ValidationError, type WebhookIntegrationConfig, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, generateContractManifest, getSharedDiscovery, isErrorType, isObjectStackError, snapToGrid, validatePluginContract };
|
|
1029
|
+
export { type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CacheStats, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, type DataMaskingConfig, type DataMaskingRule, type EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, MetadataNotFoundError, ObjectStackAdapter, ObjectStackError, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, ValidationError, type WebhookIntegrationConfig, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, generateContractManifest, getSharedDiscovery, is404Error, isErrorType, isObjectStackError, snapToGrid, validatePluginContract };
|
package/dist/index.d.ts
CHANGED
|
@@ -648,6 +648,16 @@ declare function calculateAutoLayout(items: Array<{
|
|
|
648
648
|
declare function getSharedDiscovery(baseUrl: string, fetcher: () => Promise<unknown>): Promise<unknown>;
|
|
649
649
|
/** Test/dev helper to drop the cache (e.g. on logout or origin change). */
|
|
650
650
|
declare function clearSharedDiscoveryCache(): void;
|
|
651
|
+
/**
|
|
652
|
+
* Detect "missing resource" errors regardless of where they originate.
|
|
653
|
+
*
|
|
654
|
+
* The ObjectStack client decorates thrown errors with `httpStatus` (and a
|
|
655
|
+
* machine-readable `code` such as `object_not_found`/`record_not_found`),
|
|
656
|
+
* while raw `fetch()` callers may surface `status` or `statusCode`. Treat
|
|
657
|
+
* any of these as a 404 so callers can degrade gracefully instead of
|
|
658
|
+
* tripping on the property-name mismatch.
|
|
659
|
+
*/
|
|
660
|
+
declare function is404Error(error: unknown): boolean;
|
|
651
661
|
/**
|
|
652
662
|
* Connection state for monitoring
|
|
653
663
|
*/
|
|
@@ -838,6 +848,25 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
838
848
|
* @returns Promise resolving to discovery data, or null if not connected
|
|
839
849
|
*/
|
|
840
850
|
getDiscovery(): Promise<unknown | null>;
|
|
851
|
+
/**
|
|
852
|
+
* Batch-fetch all persisted view overrides for an object.
|
|
853
|
+
*
|
|
854
|
+
* Per-view runtime overrides (density, column widths, sort, …) are
|
|
855
|
+
* stored in the metadata registry under key `<objectName>/<viewName>`.
|
|
856
|
+
* Loading them per-view fires N HTTP GETs that return 404 for views
|
|
857
|
+
* the user has never customized — generates console noise on every
|
|
858
|
+
* page load. This batch method performs a single
|
|
859
|
+
* `GET /api/v1/meta/<objectName>` (returns `{type, items}`) and
|
|
860
|
+
* returns a `{viewName: override}` map. Returns an empty map if the
|
|
861
|
+
* server doesn't expose the listing or returns no items.
|
|
862
|
+
*
|
|
863
|
+
* Result is cached identically to {@link getView}; saving a view via
|
|
864
|
+
* {@link updateViewConfig} invalidates the cache.
|
|
865
|
+
*
|
|
866
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
867
|
+
* @returns Map keyed by view name with the persisted override config
|
|
868
|
+
*/
|
|
869
|
+
listViewOverrides(objectName: string): Promise<Record<string, any>>;
|
|
841
870
|
/**
|
|
842
871
|
* Get a view definition for an object.
|
|
843
872
|
* Attempts to fetch from the server metadata API.
|
|
@@ -849,6 +878,24 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
849
878
|
* @returns Promise resolving to the view definition or null
|
|
850
879
|
*/
|
|
851
880
|
getView(objectName: string, viewId: string): Promise<unknown | null>;
|
|
881
|
+
/**
|
|
882
|
+
* Persist a view definition for an object.
|
|
883
|
+
*
|
|
884
|
+
* Symmetric counterpart to {@link getView}: writes the view to the
|
|
885
|
+
* server metadata store via `client.meta.saveItem`, then invalidates
|
|
886
|
+
* the matching cache entry so the next {@link getView} reflects the
|
|
887
|
+
* new payload. Returns the persisted item when the server echoes it,
|
|
888
|
+
* otherwise undefined.
|
|
889
|
+
*
|
|
890
|
+
* Used by ObjectView for "live" toolbar persistence (density,
|
|
891
|
+
* column widths, sort, etc.) and by the View Config Panel for
|
|
892
|
+
* explicit saves.
|
|
893
|
+
*
|
|
894
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
895
|
+
* @param viewId - View identifier (e.g. 'all_leads')
|
|
896
|
+
* @param config - Full view definition to persist
|
|
897
|
+
*/
|
|
898
|
+
updateViewConfig(objectName: string, viewId: string, config: Record<string, any>): Promise<Record<string, any> | void>;
|
|
852
899
|
/**
|
|
853
900
|
* Get an application definition by name or ID.
|
|
854
901
|
* Attempts to fetch from the server metadata API.
|
|
@@ -865,6 +912,19 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
865
912
|
* Returns null if the server doesn't support page metadata.
|
|
866
913
|
*/
|
|
867
914
|
getPage(pageId: string): Promise<unknown | null>;
|
|
915
|
+
/**
|
|
916
|
+
* Update (upsert) a dashboard definition.
|
|
917
|
+
*
|
|
918
|
+
* Dashboards are control-plane metadata, not data records. Persist via
|
|
919
|
+
* `client.meta.saveItem('dashboard', name, schema)` which routes to
|
|
920
|
+
* `PUT /api/v1/meta/dashboard/:name`. After save, invalidates the
|
|
921
|
+
* relevant metadata cache entry so the next dashboard read reflects
|
|
922
|
+
* the new payload.
|
|
923
|
+
*
|
|
924
|
+
* @param dashboardName - Dashboard identifier (e.g. 'crm_overview_dashboard')
|
|
925
|
+
* @param schema - Full dashboard schema (widgets, layout, etc.)
|
|
926
|
+
*/
|
|
927
|
+
updateDashboard(dashboardName: string, schema: Record<string, any>): Promise<Record<string, any> | void>;
|
|
868
928
|
/**
|
|
869
929
|
* Perform server-side aggregation via the ObjectStack analytics API.
|
|
870
930
|
* Uses `this.client.analytics.query()` from @objectstack/client to leverage
|
|
@@ -966,4 +1026,4 @@ declare function createObjectStackAdapter<T = unknown>(config: {
|
|
|
966
1026
|
reconnectDelay?: number;
|
|
967
1027
|
}): DataSource<T>;
|
|
968
1028
|
|
|
969
|
-
export { type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CacheStats, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, type DataMaskingConfig, type DataMaskingRule, type EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, MetadataNotFoundError, ObjectStackAdapter, ObjectStackError, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, ValidationError, type WebhookIntegrationConfig, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, generateContractManifest, getSharedDiscovery, isErrorType, isObjectStackError, snapToGrid, validatePluginContract };
|
|
1029
|
+
export { type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CacheStats, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, type DataMaskingConfig, type DataMaskingRule, type EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, MetadataNotFoundError, ObjectStackAdapter, ObjectStackError, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, ValidationError, type WebhookIntegrationConfig, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, generateContractManifest, getSharedDiscovery, is404Error, isErrorType, isObjectStackError, snapToGrid, validatePluginContract };
|
package/dist/index.js
CHANGED
|
@@ -853,6 +853,44 @@ async function getSharedDiscovery(baseUrl, fetcher) {
|
|
|
853
853
|
function clearSharedDiscoveryCache() {
|
|
854
854
|
discoveryCache.clear();
|
|
855
855
|
}
|
|
856
|
+
function is404Error(error) {
|
|
857
|
+
if (!error || typeof error !== "object") return false;
|
|
858
|
+
const err = error;
|
|
859
|
+
if (err.httpStatus === 404 || err.status === 404 || err.statusCode === 404) {
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
const code = typeof err.code === "string" ? err.code : "";
|
|
863
|
+
return code === "object_not_found" || code === "record_not_found";
|
|
864
|
+
}
|
|
865
|
+
function createQuietHttpLogger() {
|
|
866
|
+
const isExpected404 = (meta) => {
|
|
867
|
+
if (!meta || typeof meta !== "object") return false;
|
|
868
|
+
if (meta.status === 404 || meta.statusCode === 404) return true;
|
|
869
|
+
const errBody = meta.error;
|
|
870
|
+
if (errBody && typeof errBody === "object") {
|
|
871
|
+
const code = errBody.code;
|
|
872
|
+
if (code === "object_not_found" || code === "record_not_found") return true;
|
|
873
|
+
}
|
|
874
|
+
return false;
|
|
875
|
+
};
|
|
876
|
+
const logger = {
|
|
877
|
+
debug: (message, meta) => console.debug(message, meta ?? ""),
|
|
878
|
+
info: (message, meta) => console.info(message, meta ?? ""),
|
|
879
|
+
warn: (message, meta) => console.warn(message, meta ?? ""),
|
|
880
|
+
error: (message, error, meta) => {
|
|
881
|
+
if (isExpected404(meta)) {
|
|
882
|
+
console.debug(`[ObjectStack] ${message} (suppressed expected 404)`, meta);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
console.error(message, error ?? "", meta ?? "");
|
|
886
|
+
},
|
|
887
|
+
fatal: (message, error, meta) => console.error(message, error ?? "", meta ?? ""),
|
|
888
|
+
log: (message, ...args) => console.log(message, ...args),
|
|
889
|
+
child: () => logger,
|
|
890
|
+
withTrace: () => logger
|
|
891
|
+
};
|
|
892
|
+
return logger;
|
|
893
|
+
}
|
|
856
894
|
function stableStringify(value) {
|
|
857
895
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
858
896
|
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
@@ -881,7 +919,7 @@ var ObjectStackAdapter = class {
|
|
|
881
919
|
// multiple sibling components requesting the same dataset on first paint)
|
|
882
920
|
// into a single network round trip.
|
|
883
921
|
__publicField(this, "inflightFinds", /* @__PURE__ */ new Map());
|
|
884
|
-
this.client = new ObjectStackClient(config);
|
|
922
|
+
this.client = new ObjectStackClient({ ...config, logger: createQuietHttpLogger() });
|
|
885
923
|
this.metadataCache = new MetadataCache(config.cache);
|
|
886
924
|
this.autoReconnect = config.autoReconnect ?? true;
|
|
887
925
|
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 3;
|
|
@@ -1032,11 +1070,12 @@ var ObjectStackAdapter = class {
|
|
|
1032
1070
|
return this.normalizeQueryResult(result, params);
|
|
1033
1071
|
})();
|
|
1034
1072
|
this.inflightFinds.set(key, promise);
|
|
1035
|
-
|
|
1073
|
+
const cleanup = () => {
|
|
1036
1074
|
if (this.inflightFinds.get(key) === promise) {
|
|
1037
1075
|
this.inflightFinds.delete(key);
|
|
1038
1076
|
}
|
|
1039
|
-
}
|
|
1077
|
+
};
|
|
1078
|
+
promise.then(cleanup, cleanup);
|
|
1040
1079
|
return promise;
|
|
1041
1080
|
}
|
|
1042
1081
|
/**
|
|
@@ -1059,7 +1098,7 @@ var ObjectStackAdapter = class {
|
|
|
1059
1098
|
const records = resultObj.records || resultObj.value || [];
|
|
1060
1099
|
return records[0] || null;
|
|
1061
1100
|
} catch (error) {
|
|
1062
|
-
if (error
|
|
1101
|
+
if (is404Error(error)) {
|
|
1063
1102
|
return null;
|
|
1064
1103
|
}
|
|
1065
1104
|
}
|
|
@@ -1068,7 +1107,7 @@ var ObjectStackAdapter = class {
|
|
|
1068
1107
|
const result = await this.client.data.get(resource, String(id));
|
|
1069
1108
|
return result.record;
|
|
1070
1109
|
} catch (error) {
|
|
1071
|
-
if (error
|
|
1110
|
+
if (is404Error(error)) {
|
|
1072
1111
|
return null;
|
|
1073
1112
|
}
|
|
1074
1113
|
throw error;
|
|
@@ -1392,7 +1431,7 @@ var ObjectStackAdapter = class {
|
|
|
1392
1431
|
return schema;
|
|
1393
1432
|
} catch (error) {
|
|
1394
1433
|
const errorObj = error;
|
|
1395
|
-
if (errorObj
|
|
1434
|
+
if (is404Error(errorObj)) {
|
|
1396
1435
|
throw new MetadataNotFoundError(objectName, { originalError: error });
|
|
1397
1436
|
}
|
|
1398
1437
|
if (error instanceof ObjectStackError) {
|
|
@@ -1424,6 +1463,43 @@ var ObjectStackAdapter = class {
|
|
|
1424
1463
|
return null;
|
|
1425
1464
|
}
|
|
1426
1465
|
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Batch-fetch all persisted view overrides for an object.
|
|
1468
|
+
*
|
|
1469
|
+
* Per-view runtime overrides (density, column widths, sort, …) are
|
|
1470
|
+
* stored in the metadata registry under key `<objectName>/<viewName>`.
|
|
1471
|
+
* Loading them per-view fires N HTTP GETs that return 404 for views
|
|
1472
|
+
* the user has never customized — generates console noise on every
|
|
1473
|
+
* page load. This batch method performs a single
|
|
1474
|
+
* `GET /api/v1/meta/<objectName>` (returns `{type, items}`) and
|
|
1475
|
+
* returns a `{viewName: override}` map. Returns an empty map if the
|
|
1476
|
+
* server doesn't expose the listing or returns no items.
|
|
1477
|
+
*
|
|
1478
|
+
* Result is cached identically to {@link getView}; saving a view via
|
|
1479
|
+
* {@link updateViewConfig} invalidates the cache.
|
|
1480
|
+
*
|
|
1481
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
1482
|
+
* @returns Map keyed by view name with the persisted override config
|
|
1483
|
+
*/
|
|
1484
|
+
async listViewOverrides(objectName) {
|
|
1485
|
+
await this.connect();
|
|
1486
|
+
try {
|
|
1487
|
+
const cacheKey = `view-overrides:${objectName}`;
|
|
1488
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1489
|
+
const result = await this.client.meta.getItems(objectName);
|
|
1490
|
+
const items = Array.isArray(result?.items) ? result.items : [];
|
|
1491
|
+
const out = {};
|
|
1492
|
+
for (const it of items) {
|
|
1493
|
+
if (!it || typeof it !== "object") continue;
|
|
1494
|
+
const key = it.name ?? it.id ?? it._name;
|
|
1495
|
+
if (typeof key === "string" && key) out[key] = it;
|
|
1496
|
+
}
|
|
1497
|
+
return out;
|
|
1498
|
+
});
|
|
1499
|
+
} catch {
|
|
1500
|
+
return {};
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1427
1503
|
/**
|
|
1428
1504
|
* Get a view definition for an object.
|
|
1429
1505
|
* Attempts to fetch from the server metadata API.
|
|
@@ -1439,7 +1515,7 @@ var ObjectStackAdapter = class {
|
|
|
1439
1515
|
try {
|
|
1440
1516
|
const cacheKey = `view:${objectName}:${viewId}`;
|
|
1441
1517
|
return await this.metadataCache.get(cacheKey, async () => {
|
|
1442
|
-
const result = await this.client.meta.getItem(objectName,
|
|
1518
|
+
const result = await this.client.meta.getItem(objectName, viewId);
|
|
1443
1519
|
if (result && result.item) return result.item;
|
|
1444
1520
|
return result ?? null;
|
|
1445
1521
|
});
|
|
@@ -1447,6 +1523,40 @@ var ObjectStackAdapter = class {
|
|
|
1447
1523
|
return null;
|
|
1448
1524
|
}
|
|
1449
1525
|
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Persist a view definition for an object.
|
|
1528
|
+
*
|
|
1529
|
+
* Symmetric counterpart to {@link getView}: writes the view to the
|
|
1530
|
+
* server metadata store via `client.meta.saveItem`, then invalidates
|
|
1531
|
+
* the matching cache entry so the next {@link getView} reflects the
|
|
1532
|
+
* new payload. Returns the persisted item when the server echoes it,
|
|
1533
|
+
* otherwise undefined.
|
|
1534
|
+
*
|
|
1535
|
+
* Used by ObjectView for "live" toolbar persistence (density,
|
|
1536
|
+
* column widths, sort, etc.) and by the View Config Panel for
|
|
1537
|
+
* explicit saves.
|
|
1538
|
+
*
|
|
1539
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
1540
|
+
* @param viewId - View identifier (e.g. 'all_leads')
|
|
1541
|
+
* @param config - Full view definition to persist
|
|
1542
|
+
*/
|
|
1543
|
+
async updateViewConfig(objectName, viewId, config) {
|
|
1544
|
+
await this.connect();
|
|
1545
|
+
try {
|
|
1546
|
+
const result = await this.client.meta.saveItem(
|
|
1547
|
+
objectName,
|
|
1548
|
+
viewId,
|
|
1549
|
+
config
|
|
1550
|
+
);
|
|
1551
|
+
const cacheKey = `view:${objectName}:${viewId}`;
|
|
1552
|
+
this.metadataCache.invalidate?.(cacheKey);
|
|
1553
|
+
this.metadataCache.invalidate?.(`view-overrides:${objectName}`);
|
|
1554
|
+
if (result && result.item) return result.item;
|
|
1555
|
+
return result ?? void 0;
|
|
1556
|
+
} catch (err) {
|
|
1557
|
+
throw err;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1450
1560
|
/**
|
|
1451
1561
|
* Get an application definition by name or ID.
|
|
1452
1562
|
* Attempts to fetch from the server metadata API.
|
|
@@ -1487,6 +1597,34 @@ var ObjectStackAdapter = class {
|
|
|
1487
1597
|
return null;
|
|
1488
1598
|
}
|
|
1489
1599
|
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Update (upsert) a dashboard definition.
|
|
1602
|
+
*
|
|
1603
|
+
* Dashboards are control-plane metadata, not data records. Persist via
|
|
1604
|
+
* `client.meta.saveItem('dashboard', name, schema)` which routes to
|
|
1605
|
+
* `PUT /api/v1/meta/dashboard/:name`. After save, invalidates the
|
|
1606
|
+
* relevant metadata cache entry so the next dashboard read reflects
|
|
1607
|
+
* the new payload.
|
|
1608
|
+
*
|
|
1609
|
+
* @param dashboardName - Dashboard identifier (e.g. 'crm_overview_dashboard')
|
|
1610
|
+
* @param schema - Full dashboard schema (widgets, layout, etc.)
|
|
1611
|
+
*/
|
|
1612
|
+
async updateDashboard(dashboardName, schema) {
|
|
1613
|
+
await this.connect();
|
|
1614
|
+
try {
|
|
1615
|
+
const result = await this.client.meta.saveItem(
|
|
1616
|
+
"dashboard",
|
|
1617
|
+
dashboardName,
|
|
1618
|
+
schema
|
|
1619
|
+
);
|
|
1620
|
+
this.metadataCache.invalidate?.("dashboards");
|
|
1621
|
+
this.metadataCache.invalidate?.(`dashboard:${dashboardName}`);
|
|
1622
|
+
if (result && result.item) return result.item;
|
|
1623
|
+
return result ?? void 0;
|
|
1624
|
+
} catch (err) {
|
|
1625
|
+
throw err;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1490
1628
|
/**
|
|
1491
1629
|
* Perform server-side aggregation via the ObjectStack analytics API.
|
|
1492
1630
|
* Uses `this.client.analytics.query()` from @objectstack/client to leverage
|
|
@@ -1505,7 +1643,7 @@ var ObjectStackAdapter = class {
|
|
|
1505
1643
|
dimensions: params.groupBy && params.groupBy !== "_all" ? [params.groupBy] : []
|
|
1506
1644
|
};
|
|
1507
1645
|
if (params.filter) {
|
|
1508
|
-
payload.
|
|
1646
|
+
payload.where = params.filter;
|
|
1509
1647
|
}
|
|
1510
1648
|
const data = await this.client.analytics.query(payload);
|
|
1511
1649
|
const rawRows = Array.isArray(data) ? data : data?.rows && Array.isArray(data.rows) ? data.rows : data?.data && Array.isArray(data.data) ? data.data : data?.data?.rows && Array.isArray(data.data.rows) ? data.data.rows : data?.results && Array.isArray(data.results) ? data.results : [];
|
|
@@ -1516,7 +1654,7 @@ var ObjectStackAdapter = class {
|
|
|
1516
1654
|
return true;
|
|
1517
1655
|
});
|
|
1518
1656
|
if (measureMissing) {
|
|
1519
|
-
const result = await this.find(resource);
|
|
1657
|
+
const result = await this.find(resource, params.filter ? { $filter: params.filter } : void 0);
|
|
1520
1658
|
const records = result.data || [];
|
|
1521
1659
|
if (records.length === 0) return [];
|
|
1522
1660
|
return this.aggregateClientSide(records, params);
|
|
@@ -1530,7 +1668,7 @@ var ObjectStackAdapter = class {
|
|
|
1530
1668
|
return mapped;
|
|
1531
1669
|
});
|
|
1532
1670
|
} catch {
|
|
1533
|
-
const result = await this.find(resource);
|
|
1671
|
+
const result = await this.find(resource, params.filter ? { $filter: params.filter } : void 0);
|
|
1534
1672
|
const records = result.data || [];
|
|
1535
1673
|
if (records.length === 0) return [];
|
|
1536
1674
|
return this.aggregateClientSide(records, params);
|
|
@@ -1728,6 +1866,7 @@ export {
|
|
|
1728
1866
|
createObjectStackAdapter,
|
|
1729
1867
|
generateContractManifest,
|
|
1730
1868
|
getSharedDiscovery,
|
|
1869
|
+
is404Error,
|
|
1731
1870
|
isErrorType,
|
|
1732
1871
|
isObjectStackError,
|
|
1733
1872
|
snapToGrid,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/data-objectstack",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.8",
|
|
4
4
|
"description": "ObjectStack Data Adapter for Object UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@objectstack/client": "^4.0.
|
|
26
|
-
"@object-ui/core": "4.0.
|
|
27
|
-
"@object-ui/types": "4.0.
|
|
25
|
+
"@objectstack/client": "^4.0.5",
|
|
26
|
+
"@object-ui/core": "4.0.8",
|
|
27
|
+
"@object-ui/types": "4.0.8"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"tsup": "^8.5.1",
|
|
31
31
|
"typescript": "^6.0.3",
|
|
32
|
-
"vitest": "^4.1.
|
|
32
|
+
"vitest": "^4.1.6"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -110,6 +110,70 @@ export function clearSharedDiscoveryCache(): void {
|
|
|
110
110
|
discoveryCache.clear();
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Detect "missing resource" errors regardless of where they originate.
|
|
115
|
+
*
|
|
116
|
+
* The ObjectStack client decorates thrown errors with `httpStatus` (and a
|
|
117
|
+
* machine-readable `code` such as `object_not_found`/`record_not_found`),
|
|
118
|
+
* while raw `fetch()` callers may surface `status` or `statusCode`. Treat
|
|
119
|
+
* any of these as a 404 so callers can degrade gracefully instead of
|
|
120
|
+
* tripping on the property-name mismatch.
|
|
121
|
+
*/
|
|
122
|
+
export function is404Error(error: unknown): boolean {
|
|
123
|
+
if (!error || typeof error !== 'object') return false;
|
|
124
|
+
const err = error as Record<string, unknown>;
|
|
125
|
+
if (err.httpStatus === 404 || err.status === 404 || err.statusCode === 404) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
const code = typeof err.code === 'string' ? err.code : '';
|
|
129
|
+
return code === 'object_not_found' || code === 'record_not_found';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Build a Logger compatible with @objectstack/client that demotes expected
|
|
134
|
+
* 404 noise to console.debug. The client logs every non-2xx response with
|
|
135
|
+
* `logger.error("HTTP request failed", undefined, { status, error })`, but
|
|
136
|
+
* 404s on optional collections (sys_presence, sys_activity, …) are part of
|
|
137
|
+
* normal degraded operation when those plugins aren't installed on the
|
|
138
|
+
* server — they should not surface as errors in the browser DevTools.
|
|
139
|
+
*
|
|
140
|
+
* Returned object is loosely typed because the spec's Logger interface lives
|
|
141
|
+
* in a transitive package; using `any` keeps us decoupled.
|
|
142
|
+
*/
|
|
143
|
+
function createQuietHttpLogger(): any {
|
|
144
|
+
const isExpected404 = (meta?: Record<string, any>): boolean => {
|
|
145
|
+
if (!meta || typeof meta !== 'object') return false;
|
|
146
|
+
if (meta.status === 404 || meta.statusCode === 404) return true;
|
|
147
|
+
const errBody = meta.error;
|
|
148
|
+
if (errBody && typeof errBody === 'object') {
|
|
149
|
+
const code = (errBody as Record<string, unknown>).code;
|
|
150
|
+
if (code === 'object_not_found' || code === 'record_not_found') return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
};
|
|
154
|
+
const logger: any = {
|
|
155
|
+
debug: (message: string, meta?: Record<string, any>) =>
|
|
156
|
+
console.debug(message, meta ?? ''),
|
|
157
|
+
info: (message: string, meta?: Record<string, any>) =>
|
|
158
|
+
console.info(message, meta ?? ''),
|
|
159
|
+
warn: (message: string, meta?: Record<string, any>) =>
|
|
160
|
+
console.warn(message, meta ?? ''),
|
|
161
|
+
error: (message: string, error?: Error, meta?: Record<string, any>) => {
|
|
162
|
+
if (isExpected404(meta)) {
|
|
163
|
+
console.debug(`[ObjectStack] ${message} (suppressed expected 404)`, meta);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
console.error(message, error ?? '', meta ?? '');
|
|
167
|
+
},
|
|
168
|
+
fatal: (message: string, error?: Error, meta?: Record<string, any>) =>
|
|
169
|
+
console.error(message, error ?? '', meta ?? ''),
|
|
170
|
+
log: (message: string, ...args: any[]) => console.log(message, ...args),
|
|
171
|
+
child: () => logger,
|
|
172
|
+
withTrace: () => logger,
|
|
173
|
+
};
|
|
174
|
+
return logger;
|
|
175
|
+
}
|
|
176
|
+
|
|
113
177
|
/**
|
|
114
178
|
* Connection state for monitoring
|
|
115
179
|
*/
|
|
@@ -224,7 +288,11 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
224
288
|
maxReconnectAttempts?: number;
|
|
225
289
|
reconnectDelay?: number;
|
|
226
290
|
}) {
|
|
227
|
-
|
|
291
|
+
// Inject a quiet logger that demotes expected 404s ("HTTP request failed"
|
|
292
|
+
// from probing optional collections like sys_presence/sys_activity) to
|
|
293
|
+
// debug() so they don't pollute the browser console. Other log levels are
|
|
294
|
+
// forwarded to the standard console.
|
|
295
|
+
this.client = new ObjectStackClient({ ...config, logger: createQuietHttpLogger() });
|
|
228
296
|
this.metadataCache = new MetadataCache(config.cache);
|
|
229
297
|
this.autoReconnect = config.autoReconnect ?? true;
|
|
230
298
|
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 3;
|
|
@@ -426,13 +494,19 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
426
494
|
})();
|
|
427
495
|
|
|
428
496
|
this.inflightFinds.set(key, promise);
|
|
429
|
-
|
|
497
|
+
// Use `.then(cleanup, cleanup)` instead of `.finally(cleanup)`. `.finally`
|
|
498
|
+
// returns a new chained promise that re-raises the rejection, and because
|
|
499
|
+
// we don't return that chain, Node/browsers see it as an unhandled
|
|
500
|
+
// rejection — flooding DevTools when callers handle the original `promise`
|
|
501
|
+
// via `.catch()` (e.g. AppHeader probing optional sys_presence/sys_activity).
|
|
502
|
+
const cleanup = () => {
|
|
430
503
|
// Only clear if the entry still points at this promise; a later call
|
|
431
504
|
// that started after settle may have already replaced it.
|
|
432
505
|
if (this.inflightFinds.get(key) === promise) {
|
|
433
506
|
this.inflightFinds.delete(key);
|
|
434
507
|
}
|
|
435
|
-
}
|
|
508
|
+
};
|
|
509
|
+
promise.then(cleanup, cleanup);
|
|
436
510
|
return promise;
|
|
437
511
|
}
|
|
438
512
|
|
|
@@ -461,7 +535,7 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
461
535
|
const records = resultObj.records || resultObj.value || [];
|
|
462
536
|
return records[0] || null;
|
|
463
537
|
} catch (error: unknown) {
|
|
464
|
-
if ((error
|
|
538
|
+
if (is404Error(error)) {
|
|
465
539
|
return null;
|
|
466
540
|
}
|
|
467
541
|
// Fall through to direct GET without $expand — some servers don't
|
|
@@ -475,7 +549,7 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
475
549
|
return result.record;
|
|
476
550
|
} catch (error: unknown) {
|
|
477
551
|
// If record not found, return null instead of throwing
|
|
478
|
-
if ((error
|
|
552
|
+
if (is404Error(error)) {
|
|
479
553
|
return null;
|
|
480
554
|
}
|
|
481
555
|
throw error;
|
|
@@ -908,7 +982,7 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
908
982
|
} catch (error: unknown) {
|
|
909
983
|
// Check if it's a 404 error
|
|
910
984
|
const errorObj = error as Record<string, unknown>;
|
|
911
|
-
if (errorObj
|
|
985
|
+
if (is404Error(errorObj)) {
|
|
912
986
|
throw new MetadataNotFoundError(objectName, { originalError: error });
|
|
913
987
|
}
|
|
914
988
|
|
|
@@ -952,6 +1026,45 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
952
1026
|
}
|
|
953
1027
|
}
|
|
954
1028
|
|
|
1029
|
+
/**
|
|
1030
|
+
* Batch-fetch all persisted view overrides for an object.
|
|
1031
|
+
*
|
|
1032
|
+
* Per-view runtime overrides (density, column widths, sort, …) are
|
|
1033
|
+
* stored in the metadata registry under key `<objectName>/<viewName>`.
|
|
1034
|
+
* Loading them per-view fires N HTTP GETs that return 404 for views
|
|
1035
|
+
* the user has never customized — generates console noise on every
|
|
1036
|
+
* page load. This batch method performs a single
|
|
1037
|
+
* `GET /api/v1/meta/<objectName>` (returns `{type, items}`) and
|
|
1038
|
+
* returns a `{viewName: override}` map. Returns an empty map if the
|
|
1039
|
+
* server doesn't expose the listing or returns no items.
|
|
1040
|
+
*
|
|
1041
|
+
* Result is cached identically to {@link getView}; saving a view via
|
|
1042
|
+
* {@link updateViewConfig} invalidates the cache.
|
|
1043
|
+
*
|
|
1044
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
1045
|
+
* @returns Map keyed by view name with the persisted override config
|
|
1046
|
+
*/
|
|
1047
|
+
async listViewOverrides(objectName: string): Promise<Record<string, any>> {
|
|
1048
|
+
await this.connect();
|
|
1049
|
+
|
|
1050
|
+
try {
|
|
1051
|
+
const cacheKey = `view-overrides:${objectName}`;
|
|
1052
|
+
return await this.metadataCache.get(cacheKey, async () => {
|
|
1053
|
+
const result: any = await this.client.meta.getItems(objectName);
|
|
1054
|
+
const items: any[] = Array.isArray(result?.items) ? result.items : [];
|
|
1055
|
+
const out: Record<string, any> = {};
|
|
1056
|
+
for (const it of items) {
|
|
1057
|
+
if (!it || typeof it !== 'object') continue;
|
|
1058
|
+
const key = it.name ?? it.id ?? it._name;
|
|
1059
|
+
if (typeof key === 'string' && key) out[key] = it;
|
|
1060
|
+
}
|
|
1061
|
+
return out;
|
|
1062
|
+
});
|
|
1063
|
+
} catch {
|
|
1064
|
+
return {};
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
955
1068
|
/**
|
|
956
1069
|
* Get a view definition for an object.
|
|
957
1070
|
* Attempts to fetch from the server metadata API.
|
|
@@ -969,7 +1082,7 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
969
1082
|
const cacheKey = `view:${objectName}:${viewId}`;
|
|
970
1083
|
return await this.metadataCache.get(cacheKey, async () => {
|
|
971
1084
|
// Try meta.getItem for view metadata
|
|
972
|
-
const result: any = await this.client.meta.getItem(objectName,
|
|
1085
|
+
const result: any = await this.client.meta.getItem(objectName, viewId);
|
|
973
1086
|
if (result && result.item) return result.item;
|
|
974
1087
|
return result ?? null;
|
|
975
1088
|
});
|
|
@@ -979,6 +1092,50 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
979
1092
|
}
|
|
980
1093
|
}
|
|
981
1094
|
|
|
1095
|
+
/**
|
|
1096
|
+
* Persist a view definition for an object.
|
|
1097
|
+
*
|
|
1098
|
+
* Symmetric counterpart to {@link getView}: writes the view to the
|
|
1099
|
+
* server metadata store via `client.meta.saveItem`, then invalidates
|
|
1100
|
+
* the matching cache entry so the next {@link getView} reflects the
|
|
1101
|
+
* new payload. Returns the persisted item when the server echoes it,
|
|
1102
|
+
* otherwise undefined.
|
|
1103
|
+
*
|
|
1104
|
+
* Used by ObjectView for "live" toolbar persistence (density,
|
|
1105
|
+
* column widths, sort, etc.) and by the View Config Panel for
|
|
1106
|
+
* explicit saves.
|
|
1107
|
+
*
|
|
1108
|
+
* @param objectName - Object name (e.g. 'lead')
|
|
1109
|
+
* @param viewId - View identifier (e.g. 'all_leads')
|
|
1110
|
+
* @param config - Full view definition to persist
|
|
1111
|
+
*/
|
|
1112
|
+
async updateViewConfig(
|
|
1113
|
+
objectName: string,
|
|
1114
|
+
viewId: string,
|
|
1115
|
+
config: Record<string, any>
|
|
1116
|
+
): Promise<Record<string, any> | void> {
|
|
1117
|
+
await this.connect();
|
|
1118
|
+
try {
|
|
1119
|
+
const result: any = await this.client.meta.saveItem(
|
|
1120
|
+
objectName,
|
|
1121
|
+
viewId,
|
|
1122
|
+
config
|
|
1123
|
+
);
|
|
1124
|
+
// Invalidate cached read so next getView reflects the change
|
|
1125
|
+
const cacheKey = `view:${objectName}:${viewId}`;
|
|
1126
|
+
this.metadataCache.invalidate?.(cacheKey);
|
|
1127
|
+
// Also invalidate the batch override map so listViewOverrides re-fetches
|
|
1128
|
+
this.metadataCache.invalidate?.(`view-overrides:${objectName}`);
|
|
1129
|
+
if (result && result.item) return result.item;
|
|
1130
|
+
return result ?? undefined;
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
// Surface the error so the caller can decide whether to toast/log;
|
|
1133
|
+
// we don't swallow it here because persistence failures are
|
|
1134
|
+
// operationally meaningful (unlike read fallbacks).
|
|
1135
|
+
throw err;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
982
1139
|
/**
|
|
983
1140
|
* Get an application definition by name or ID.
|
|
984
1141
|
* Attempts to fetch from the server metadata API.
|
|
@@ -1025,6 +1182,40 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1025
1182
|
}
|
|
1026
1183
|
}
|
|
1027
1184
|
|
|
1185
|
+
/**
|
|
1186
|
+
* Update (upsert) a dashboard definition.
|
|
1187
|
+
*
|
|
1188
|
+
* Dashboards are control-plane metadata, not data records. Persist via
|
|
1189
|
+
* `client.meta.saveItem('dashboard', name, schema)` which routes to
|
|
1190
|
+
* `PUT /api/v1/meta/dashboard/:name`. After save, invalidates the
|
|
1191
|
+
* relevant metadata cache entry so the next dashboard read reflects
|
|
1192
|
+
* the new payload.
|
|
1193
|
+
*
|
|
1194
|
+
* @param dashboardName - Dashboard identifier (e.g. 'crm_overview_dashboard')
|
|
1195
|
+
* @param schema - Full dashboard schema (widgets, layout, etc.)
|
|
1196
|
+
*/
|
|
1197
|
+
async updateDashboard(
|
|
1198
|
+
dashboardName: string,
|
|
1199
|
+
schema: Record<string, any>
|
|
1200
|
+
): Promise<Record<string, any> | void> {
|
|
1201
|
+
await this.connect();
|
|
1202
|
+
try {
|
|
1203
|
+
const result: any = await this.client.meta.saveItem(
|
|
1204
|
+
'dashboard',
|
|
1205
|
+
dashboardName,
|
|
1206
|
+
schema
|
|
1207
|
+
);
|
|
1208
|
+
// Invalidate dashboards list and any cached dashboard read so the
|
|
1209
|
+
// next render reflects the change.
|
|
1210
|
+
this.metadataCache.invalidate?.('dashboards');
|
|
1211
|
+
this.metadataCache.invalidate?.(`dashboard:${dashboardName}`);
|
|
1212
|
+
if (result && result.item) return result.item;
|
|
1213
|
+
return result ?? undefined;
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
throw err;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1028
1219
|
/**
|
|
1029
1220
|
* Perform server-side aggregation via the ObjectStack analytics API.
|
|
1030
1221
|
* Uses `this.client.analytics.query()` from @objectstack/client to leverage
|
|
@@ -1051,7 +1242,11 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1051
1242
|
dimensions: params.groupBy && params.groupBy !== '_all' ? [params.groupBy] : [],
|
|
1052
1243
|
};
|
|
1053
1244
|
if (params.filter) {
|
|
1054
|
-
|
|
1245
|
+
// Dashboard widgets emit MongoDB-style FilterCondition (per
|
|
1246
|
+
// spec/ui/dashboard.zod.ts). Send via the canonical `where`
|
|
1247
|
+
// field of the analytics endpoint, matching the unified Query
|
|
1248
|
+
// DSL (spec/data/query.zod.ts).
|
|
1249
|
+
payload.where = params.filter;
|
|
1055
1250
|
}
|
|
1056
1251
|
|
|
1057
1252
|
const data = await this.client.analytics.query(payload);
|
|
@@ -1074,7 +1269,7 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1074
1269
|
return true;
|
|
1075
1270
|
});
|
|
1076
1271
|
if (measureMissing) {
|
|
1077
|
-
const result = await this.find(resource as any);
|
|
1272
|
+
const result = await this.find(resource as any, params.filter ? { $filter: params.filter } as any : undefined);
|
|
1078
1273
|
const records = result.data || [];
|
|
1079
1274
|
if (records.length === 0) return [];
|
|
1080
1275
|
return this.aggregateClientSide(records, params);
|
|
@@ -1094,8 +1289,11 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1094
1289
|
});
|
|
1095
1290
|
} catch {
|
|
1096
1291
|
// If the analytics endpoint is not available, fall back to
|
|
1097
|
-
// find() + client-side aggregation
|
|
1098
|
-
|
|
1292
|
+
// find() + client-side aggregation. Crucially, forward the same
|
|
1293
|
+
// filter so the fallback aggregates over the SAME row set the
|
|
1294
|
+
// server-side analytics query would have — otherwise the KPI
|
|
1295
|
+
// silently sums the whole table and lies to the user.
|
|
1296
|
+
const result = await this.find(resource as any, params.filter ? { $filter: params.filter } as any : undefined);
|
|
1099
1297
|
const records = result.data || [];
|
|
1100
1298
|
if (records.length === 0) return [];
|
|
1101
1299
|
|