@object-ui/data-objectstack 17.1.0 → 17.3.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.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ObjectStackClient } from '@objectstack/client';
2
+ import { DroppedFieldsEvent } from '@objectstack/spec/data';
3
+ export { DroppedFieldsEvent } from '@objectstack/spec/data';
2
4
  import { DataSource, QueryParams, QueryResult, GlobalSearchResult, DataSourceMutationEvent, BatchTransactionOperation, ImportRequestOptions, ImportRecordsResult, CreateImportJobResult, ImportJobProgressInfo, ImportJobResultsInfo, ListImportJobsOptions, ImportJobSummaryInfo, ImportJobUndoResult, ExportDownloadRequest, FileUploadResult } from '@object-ui/types';
3
5
  export { FileUploadResult } from '@object-ui/types';
4
6
 
@@ -95,16 +97,28 @@ declare class AuthenticationError extends ObjectStackError {
95
97
  constructor(message?: string, details?: Record<string, unknown>, statusCode?: number);
96
98
  }
97
99
  /**
98
- * Error thrown when data validation fails
100
+ * Error thrown when the ObjectStack data API rejects a write as invalid.
101
+ *
102
+ * NOT the spec's `ValidationError` (`@objectstack/spec/kernel`), whose name this
103
+ * class wore until objectui#3160 (objectstack#4115 ledger batch 6). That one is
104
+ * a plain DATA SHAPE — `{ field, message, code? }`, one entry in a plugin
105
+ * manifest's validation report — and `@object-ui/types` re-exports it under that
106
+ * name. This is a runtime `Error` subclass carrying an HTTP status plus a list
107
+ * of such entries, so the two are not even the same KIND of thing.
108
+ *
109
+ * The name follows the convention registered on objectstack#4115 for this
110
+ * family — `<what was validated>Validation<Error|Result>`. `@object-ui/core`
111
+ * took `SchemaNodeValidationError` for its SDUI-tree walk; this one belongs to
112
+ * the data API.
99
113
  */
100
- declare class ValidationError extends ObjectStackError {
114
+ declare class DataApiValidationError extends ObjectStackError {
101
115
  field?: string | undefined;
102
116
  validationErrors?: Array<{
103
117
  field: string;
104
118
  message: string;
105
119
  }> | undefined;
106
120
  /**
107
- * Create a new ValidationError
121
+ * Create a new DataApiValidationError
108
122
  *
109
123
  * @param message - Human-readable error message
110
124
  * @param field - The field that failed validation (optional)
@@ -148,9 +162,18 @@ declare function isErrorType<T extends ObjectStackError>(error: unknown, errorCl
148
162
  * LICENSE file in the root directory of this source tree.
149
163
  */
150
164
  /**
151
- * Cache statistics for monitoring
165
+ * Statistics reported by {@link MetadataCache.getStats} for monitoring.
166
+ *
167
+ * NOT the spec's `CacheStats` (`@objectstack/spec/contracts`), whose name this
168
+ * interface wore until objectui#3160 (objectstack#4115 ledger batch 6). That one
169
+ * describes the platform's `ICacheService` — a server-side KV cache measured by
170
+ * `keyCount` and `memoryUsage`. This one describes the browser-side LRU in front
171
+ * of `/api/v1/meta/*`: it is bounded (`size`/`maxSize`), it evicts, it coalesces
172
+ * concurrent fetches onto one in-flight promise, and it reports a `hitRate`.
173
+ * Neither type has a key the other has, so this is a name collision, not a
174
+ * dialect — deriving would have replaced every field.
152
175
  */
153
- interface CacheStats {
176
+ interface MetadataCacheStats {
154
177
  size: number;
155
178
  maxSize: number;
156
179
  hits: number;
@@ -563,7 +586,21 @@ interface MetadataDraftHeader {
563
586
  updatedAt: string | null;
564
587
  updatedBy: string | null;
565
588
  }
566
- interface MetadataSaveOptions {
589
+ /**
590
+ * Options for {@link MetadataClient.save} — a WRITE OVER HTTP to
591
+ * `/api/v1/meta/:type/:name`.
592
+ *
593
+ * NOT the spec's `MetadataSaveOptions` (`@objectstack/spec/system` and
594
+ * `/kernel`), whose name this interface wore until objectui#3160
595
+ * (objectstack#4115 ledger batch 6). Both spec copies describe writing a
596
+ * metadata item to a FILE — `format: json|yaml|ts`, `path`, `indent`,
597
+ * `prettify`, `sortKeys`, `backup`, `atomic`, `loader`. Not one of those keys
598
+ * exists here, and not one of these exists there: this is the REST client's
599
+ * request envelope — optimistic concurrency (`ifMatch` → `If-Match`), actor
600
+ * attribution, the destructive-change override, the ADR-0033 draft/publish
601
+ * mode, and the owning package. Same words, different layer.
602
+ */
603
+ interface MetadataClientSaveOptions {
567
604
  /**
568
605
  * Optimistic concurrency token (the `checksum` returned by the last
569
606
  * read). When present, sent as the `If-Match` header so concurrent
@@ -608,7 +645,7 @@ interface MetadataGetOptions {
608
645
  */
609
646
  packageId?: string;
610
647
  }
611
- interface MetadataDeleteOptions extends MetadataSaveOptions {
648
+ interface MetadataDeleteOptions extends MetadataClientSaveOptions {
612
649
  /**
613
650
  * Target state. `'draft'` discards the pending draft (keeps the
614
651
  * published overlay intact). Omit to reset the active overlay back
@@ -847,7 +884,7 @@ declare class MetadataClient {
847
884
  * envelopes; we send bare for consistency. Pass `mode: 'draft'` to
848
885
  * stage the change without publishing (Studio's "Save" button).
849
886
  */
850
- save<T = unknown>(type: string, name: string, item: unknown, options?: MetadataSaveOptions): Promise<T>;
887
+ save<T = unknown>(type: string, name: string, item: unknown, options?: MetadataClientSaveOptions): Promise<T>;
851
888
  /**
852
889
  * Publish a single pending draft BY REFERENCE — promotes the draft row for
853
890
  * `(type, name)` into the active overlay and drops the draft. Works for ANY
@@ -957,7 +994,20 @@ declare class MetadataClient {
957
994
  * Security module integration for @objectstack/spec v3.0.0
958
995
  * Provides advanced security policies: CSP config, audit logging, data masking.
959
996
  */
960
- interface SecurityPolicy {
997
+ /**
998
+ * Configuration for {@link SecurityManager} — this adapter's browser-side
999
+ * security posture: the CSP header it generates, the in-memory audit log it
1000
+ * keeps, and the field-masking rules it applies before a record is rendered.
1001
+ *
1002
+ * NOT the spec's `SecurityPolicy` (`@objectstack/spec/kernel`), whose name this
1003
+ * interface wore until objectui#3160 (objectstack#4115 ledger batch 6). That one
1004
+ * is the PACKAGE SUPPLY-CHAIN policy — `{ id, name, autoScan, thresholds,
1005
+ * allowedLicenses, prohibitedLicenses, codeSigning, sandbox }` — the rules a
1006
+ * marketplace applies when scanning a plugin before install. It shares no key
1007
+ * with this one, so there was nothing to derive; the collision is the word
1008
+ * "security", not the concept.
1009
+ */
1010
+ interface SecurityManagerPolicy {
961
1011
  /** Content Security Policy configuration */
962
1012
  csp?: CSPConfig;
963
1013
  /** Audit logging configuration */
@@ -1031,7 +1081,7 @@ interface AuditLogEntry {
1031
1081
  declare class SecurityManager {
1032
1082
  private policy;
1033
1083
  private auditLog;
1034
- constructor(policy?: SecurityPolicy);
1084
+ constructor(policy?: SecurityManagerPolicy);
1035
1085
  /**
1036
1086
  * Generate a CSP header string from the configuration.
1037
1087
  */
@@ -1055,11 +1105,11 @@ declare class SecurityManager {
1055
1105
  /**
1056
1106
  * Update the security policy.
1057
1107
  */
1058
- updatePolicy(policy: Partial<SecurityPolicy>): void;
1108
+ updatePolicy(policy: Partial<SecurityManagerPolicy>): void;
1059
1109
  /**
1060
1110
  * Get current security policy.
1061
1111
  */
1062
- getPolicy(): SecurityPolicy;
1112
+ getPolicy(): SecurityManagerPolicy;
1063
1113
  }
1064
1114
 
1065
1115
  /**
@@ -1379,7 +1429,7 @@ declare function isConcurrentUpdateError(error: unknown): error is ConcurrentUpd
1379
1429
  *
1380
1430
  * Two shapes are recognised:
1381
1431
  * - `409` + `CONCURRENT_UPDATE` → {@link ConcurrentUpdateError};
1382
- * - `400` + `VALIDATION_FAILED` → {@link ValidationError}, carrying the
1432
+ * - `400` + `VALIDATION_FAILED` → {@link DataApiValidationError}, carrying the
1383
1433
  * server's per-field entries so a form can mark the offending inputs
1384
1434
  * instead of showing one undirected toast.
1385
1435
  */
@@ -1414,19 +1464,7 @@ type ConnectionStateListener = (event: ConnectionStateEvent) => void;
1414
1464
  * Event listener type for batch operation progress
1415
1465
  */
1416
1466
  type BatchProgressListener = (event: BatchProgressEvent) => void;
1417
- /**
1418
- * One server-reported write-strip: caller-supplied fields the backend LEGALLY
1419
- * removed from a write before persisting (a non-system caller cannot seed a
1420
- * `readonly` field, a `readonlyWhen` predicate locked it, etc.). Mirrors the
1421
- * framework `DroppedFieldsEvent` (spec `DroppedFieldsEventSchema`) structurally
1422
- * so we don't pin a client type version — `reason` is kept as a widened string
1423
- * for forward-compatibility with reasons added server-side.
1424
- */
1425
- interface DroppedFieldsEvent {
1426
- object: string;
1427
- fields: string[];
1428
- reason: string;
1429
- }
1467
+
1430
1468
  /**
1431
1469
  * Emitted after a create/update whose response carried `droppedFields`
1432
1470
  * (framework #3431/#3455). The write SUCCEEDED — this is a warning that some
@@ -2085,7 +2123,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
2085
2123
  /**
2086
2124
  * Get cache statistics for monitoring performance.
2087
2125
  */
2088
- getCacheStats(): CacheStats;
2126
+ getCacheStats(): MetadataCacheStats;
2089
2127
  /**
2090
2128
  * Invalidate metadata cache entries.
2091
2129
  *
@@ -2173,4 +2211,4 @@ declare function createObjectStackAdapter<T = unknown>(config: {
2173
2211
  reconnectDelay?: number;
2174
2212
  }): DataSource<T>;
2175
2213
 
2176
- export { AnalyticsNotInstalledError, AnalyticsQueryRejectedError, type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CacheStats, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConcurrentUpdateError, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, type DataMaskingConfig, type DataMaskingRule, type DroppedFieldsEvent, type EmailIntegrationConfig, FILTER_OPERATOR_ALIASES, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, MalformedFilterError, type MetadataAuditEntry, type MetadataAuditResponse, MetadataClient, type MetadataClientConfig, type MetadataDeleteOptions, type MetadataDiagnostics, type MetadataDiagnosticsEntry, type MetadataDiagnosticsOptions, type MetadataDiagnosticsSummary, type MetadataDraftHeader, type MetadataError, type MetadataGetOptions, type MetadataHistoryOptions, type MetadataLayered, type MetadataListOptions, MetadataNotFoundError, type MetadataReference, type MetadataSaveOptions, type MetadataValidationIssue, ObjectStackAdapter, ObjectStackError, type ObjectStackUserStateAdapterOptions, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, type UserDataAdapter, ValidationError, type WebhookIntegrationConfig, type WriteWarningEvent, type WriteWarningListener, calculateAutoLayout, classifyAnalyticsFailure, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isAnalyticsNotInstalledError, isConcurrentUpdateError, isErrorType, isMalformedFilterError, isObjectStackError, normaliseClientError, readTransactionalBatchCapability, serializeOrderBy, snapToGrid, validatePluginContract };
2214
+ export { AnalyticsNotInstalledError, AnalyticsQueryRejectedError, type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConcurrentUpdateError, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, DataApiValidationError, type DataMaskingConfig, type DataMaskingRule, type EmailIntegrationConfig, FILTER_OPERATOR_ALIASES, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, MalformedFilterError, type MetadataAuditEntry, type MetadataAuditResponse, type MetadataCacheStats, MetadataClient, type MetadataClientConfig, type MetadataClientSaveOptions, type MetadataDeleteOptions, type MetadataDiagnostics, type MetadataDiagnosticsEntry, type MetadataDiagnosticsOptions, type MetadataDiagnosticsSummary, type MetadataDraftHeader, type MetadataError, type MetadataGetOptions, type MetadataHistoryOptions, type MetadataLayered, type MetadataListOptions, MetadataNotFoundError, type MetadataReference, type MetadataValidationIssue, ObjectStackAdapter, ObjectStackError, type ObjectStackUserStateAdapterOptions, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityManagerPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, type UserDataAdapter, type WebhookIntegrationConfig, type WriteWarningEvent, type WriteWarningListener, calculateAutoLayout, classifyAnalyticsFailure, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isAnalyticsNotInstalledError, isConcurrentUpdateError, isErrorType, isMalformedFilterError, isObjectStackError, normaliseClientError, readTransactionalBatchCapability, serializeOrderBy, snapToGrid, validatePluginContract };
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ function errorCodeIsAnyOf(err, codes) {
23
23
  }
24
24
 
25
25
  // ../core/src/utils/filter-converter.ts
26
+ import { normalizeFilterOperator } from "@objectstack/spec/ui";
26
27
  var FilterOperatorError = class extends Error {
27
28
  constructor(message) {
28
29
  super(message);
@@ -880,10 +881,10 @@ var AuthenticationError = class extends ObjectStackError {
880
881
  this.name = "AuthenticationError";
881
882
  }
882
883
  };
883
- var ValidationError = class extends ObjectStackError {
884
+ var DataApiValidationError = class extends ObjectStackError {
884
885
  /**
885
- * Create a new ValidationError
886
- *
886
+ * Create a new DataApiValidationError
887
+ *
887
888
  * @param message - Human-readable error message
888
889
  * @param field - The field that failed validation (optional)
889
890
  * @param validationErrors - Array of validation error details
@@ -934,7 +935,7 @@ function createErrorFromResponse(response, context) {
934
935
  }
935
936
  return new ObjectStackError(message, "NOT_FOUND", 404, details);
936
937
  case 400:
937
- return new ValidationError(message, void 0, response?.data?.errors, details);
938
+ return new DataApiValidationError(message, void 0, response?.data?.errors, details);
938
939
  case 503:
939
940
  return new ConnectionError(message, response?.config?.url, details, 503);
940
941
  case 504:
@@ -1451,7 +1452,7 @@ var FILTER_OPERATOR_ALIASES = {
1451
1452
  before: "<",
1452
1453
  after: ">"
1453
1454
  };
1454
- function normalizeFilterOperator(op) {
1455
+ function normalizeFilterOperator2(op) {
1455
1456
  if (typeof op !== "string") return null;
1456
1457
  const lower = op.toLowerCase();
1457
1458
  return FILTER_OPERATOR_ALIASES[lower] ?? FILTER_OPERATOR_ALIASES[op] ?? op;
@@ -1480,7 +1481,7 @@ function objectFilterEntryToAST(entry) {
1480
1481
  if (!entry || typeof entry !== "object") return null;
1481
1482
  const field = entry.field;
1482
1483
  const rawOp = entry.operator ?? entry.op ?? "=";
1483
- const op = normalizeFilterOperator(rawOp);
1484
+ const op = normalizeFilterOperator2(rawOp);
1484
1485
  if (!field || !op) return null;
1485
1486
  return [String(field), op, entry.value];
1486
1487
  }
@@ -1647,7 +1648,7 @@ function normaliseClientError(error) {
1647
1648
  const message = typeof rec.message === "string" && rec.message.trim() ? rec.message : typeof rec.code === "string" ? rec.code : "";
1648
1649
  return { field, message };
1649
1650
  }).filter((x) => x !== null);
1650
- return new ValidationError(
1651
+ return new DataApiValidationError(
1651
1652
  typeof e.message === "string" ? e.message : "Validation failed",
1652
1653
  validationErrors[0]?.field,
1653
1654
  validationErrors,
@@ -1698,6 +1699,31 @@ function stableStringify(value) {
1698
1699
  const keys = Object.keys(obj).sort();
1699
1700
  return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
1700
1701
  }
1702
+ function sameWireValue(a, b) {
1703
+ if (a === b) return true;
1704
+ const aEmpty = a === null || a === void 0;
1705
+ const bEmpty = b === null || b === void 0;
1706
+ if (aEmpty || bEmpty) return aEmpty && bEmpty;
1707
+ return stableStringify(a) === stableStringify(b);
1708
+ }
1709
+ function withoutNoOpDrops(droppedFields, sent, stored) {
1710
+ if (!sent || !stored || typeof sent !== "object" || typeof stored !== "object") {
1711
+ return droppedFields;
1712
+ }
1713
+ const out = [];
1714
+ for (const e of droppedFields) {
1715
+ const kept = e.fields.filter((f) => {
1716
+ if (!Object.prototype.hasOwnProperty.call(sent, f)) return true;
1717
+ if (!Object.prototype.hasOwnProperty.call(stored, f)) return true;
1718
+ return !sameWireValue(
1719
+ sent[f],
1720
+ stored[f]
1721
+ );
1722
+ });
1723
+ if (kept.length > 0) out.push({ ...e, fields: kept });
1724
+ }
1725
+ return out;
1726
+ }
1701
1727
  var ObjectStackAdapter = class {
1702
1728
  constructor(config) {
1703
1729
  __publicField(this, "client");
@@ -2074,12 +2100,14 @@ var ObjectStackAdapter = class {
2074
2100
  * whose response type predates `droppedFields`: the field is read structurally
2075
2101
  * and validated, so an older client (or a backend that never drops) is a no-op.
2076
2102
  */
2077
- notifyDroppedFields(operation, resource, result, id) {
2103
+ notifyDroppedFields(operation, resource, result, id, sent) {
2078
2104
  const dropped = result?.droppedFields;
2079
2105
  if (!Array.isArray(dropped) || dropped.length === 0) return;
2080
- const droppedFields = dropped.filter(
2106
+ const valid = dropped.filter(
2081
2107
  (e) => !!e && typeof e === "object" && Array.isArray(e.fields) && e.fields.length > 0
2082
2108
  );
2109
+ const stored = result?.record;
2110
+ const droppedFields = withoutNoOpDrops(valid, sent, stored);
2083
2111
  if (droppedFields.length === 0) return;
2084
2112
  this.emitWriteWarning({ operation, resource, ...id !== void 0 ? { id } : {}, droppedFields });
2085
2113
  }
@@ -2098,17 +2126,25 @@ var ObjectStackAdapter = class {
2098
2126
  notifyBatchDroppedFields(operations, payload) {
2099
2127
  const dropped = payload?.droppedFields;
2100
2128
  if (!Array.isArray(dropped) || dropped.length === 0) return;
2129
+ const results = payload?.results;
2101
2130
  for (const entry of dropped) {
2102
2131
  if (!entry || typeof entry !== "object") continue;
2103
2132
  const e = entry;
2104
2133
  if (!Array.isArray(e.fields) || e.fields.length === 0) continue;
2105
2134
  const op = typeof e.index === "number" ? operations[e.index] : void 0;
2135
+ const stored = typeof e.index === "number" && Array.isArray(results) ? results[e.index] : void 0;
2136
+ const [live] = withoutNoOpDrops(
2137
+ [{ object: e.object, fields: e.fields, reason: e.reason }],
2138
+ op?.data,
2139
+ stored
2140
+ );
2141
+ if (!live) continue;
2106
2142
  const operation = (op?.action ?? "create") === "create" ? "create" : "update";
2107
2143
  this.emitWriteWarning({
2108
2144
  operation,
2109
2145
  resource: e.object ?? op?.object ?? "",
2110
2146
  ...op?.id !== void 0 && op?.id !== null ? { id: op.id } : {},
2111
- droppedFields: [{ object: e.object, fields: e.fields, reason: e.reason }]
2147
+ droppedFields: [live]
2112
2148
  });
2113
2149
  }
2114
2150
  }
@@ -2128,7 +2164,13 @@ var ObjectStackAdapter = class {
2128
2164
  try {
2129
2165
  const result = await this.client.data.create(resource, data);
2130
2166
  this.emitMutation({ type: "create", resource, record: { ...result.record } });
2131
- this.notifyDroppedFields("create", resource, result, result.record?.id);
2167
+ this.notifyDroppedFields(
2168
+ "create",
2169
+ resource,
2170
+ result,
2171
+ result.record?.id,
2172
+ data
2173
+ );
2132
2174
  return result.record;
2133
2175
  } catch (err) {
2134
2176
  throw normaliseClientError(err);
@@ -2155,7 +2197,7 @@ var ObjectStackAdapter = class {
2155
2197
  opts?.ifMatch ? { ifMatch: opts.ifMatch } : void 0
2156
2198
  );
2157
2199
  this.emitMutation({ type: "update", resource, id, record: { ...result.record } });
2158
- this.notifyDroppedFields("update", resource, result, id);
2200
+ this.notifyDroppedFields("update", resource, result, id, data);
2159
2201
  return result.record;
2160
2202
  } catch (err) {
2161
2203
  throw normaliseClientError(err);
@@ -3682,6 +3724,7 @@ export {
3682
3724
  CloudOperations,
3683
3725
  ConcurrentUpdateError,
3684
3726
  ConnectionError,
3727
+ DataApiValidationError,
3685
3728
  FILTER_OPERATOR_ALIASES,
3686
3729
  IntegrationManager,
3687
3730
  MalformedFilterError,
@@ -3690,7 +3733,6 @@ export {
3690
3733
  ObjectStackAdapter,
3691
3734
  ObjectStackError,
3692
3735
  SecurityManager,
3693
- ValidationError,
3694
3736
  calculateAutoLayout,
3695
3737
  classifyAnalyticsFailure,
3696
3738
  clearSharedDiscoveryCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/data-objectstack",
3
- "version": "17.1.0",
3
+ "version": "17.3.0",
4
4
  "description": "ObjectStack Data Adapter for Object UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,12 +22,12 @@
22
22
  "LICENSE"
23
23
  ],
24
24
  "dependencies": {
25
- "@objectstack/client": "^17.0.0-rc.0",
26
- "@object-ui/core": "17.1.0",
27
- "@object-ui/types": "17.1.0"
25
+ "@objectstack/client": "^17.0.0-rc.2",
26
+ "@objectstack/spec": "^17.0.0-rc.2",
27
+ "@object-ui/core": "17.3.0",
28
+ "@object-ui/types": "17.3.0"
28
29
  },
29
30
  "devDependencies": {
30
- "@objectstack/spec": "^17.0.0-rc.0",
31
31
  "tsup": "^8.5.1",
32
32
  "typescript": "^6.0.3",
33
33
  "vitest": "^4.1.10"
@@ -17,9 +17,18 @@ interface CachedSchema {
17
17
  }
18
18
 
19
19
  /**
20
- * Cache statistics for monitoring
20
+ * Statistics reported by {@link MetadataCache.getStats} for monitoring.
21
+ *
22
+ * NOT the spec's `CacheStats` (`@objectstack/spec/contracts`), whose name this
23
+ * interface wore until objectui#3160 (objectstack#4115 ledger batch 6). That one
24
+ * describes the platform's `ICacheService` — a server-side KV cache measured by
25
+ * `keyCount` and `memoryUsage`. This one describes the browser-side LRU in front
26
+ * of `/api/v1/meta/*`: it is bounded (`size`/`maxSize`), it evicts, it coalesces
27
+ * concurrent fetches onto one in-flight promise, and it reports a `hitRate`.
28
+ * Neither type has a key the other has, so this is a name collision, not a
29
+ * dialect — deriving would have replaced every field.
21
30
  */
22
- export interface CacheStats {
31
+ export interface MetadataCacheStats {
23
32
  size: number;
24
33
  maxSize: number;
25
34
  hits: number;
@@ -218,7 +227,7 @@ export class MetadataCache {
218
227
  *
219
228
  * @returns Cache statistics including hit rate
220
229
  */
221
- getStats(): CacheStats {
230
+ getStats(): MetadataCacheStats {
222
231
  const total = this.stats.hits + this.stats.misses;
223
232
  const hitRate = total > 0 ? this.stats.hits / total : 0;
224
233
 
@@ -13,7 +13,7 @@ import {
13
13
  BulkOperationError,
14
14
  ConnectionError,
15
15
  AuthenticationError,
16
- ValidationError,
16
+ DataApiValidationError,
17
17
  createErrorFromResponse,
18
18
  isObjectStackError,
19
19
  isErrorType,
@@ -189,20 +189,20 @@ describe('Error Classes', () => {
189
189
  });
190
190
  });
191
191
 
192
- describe('ValidationError', () => {
192
+ describe('DataApiValidationError', () => {
193
193
  it('should create validation error', () => {
194
- const error = new ValidationError('Invalid input');
194
+ const error = new DataApiValidationError('Invalid input');
195
195
 
196
196
  expect(error.message).toBe('Invalid input');
197
197
  expect(error.code).toBe('VALIDATION_ERROR');
198
198
  expect(error.statusCode).toBe(400);
199
199
  expect(error.name).toBe('ValidationError');
200
200
  expect(error).toBeInstanceOf(ObjectStackError);
201
- expect(error).toBeInstanceOf(ValidationError);
201
+ expect(error).toBeInstanceOf(DataApiValidationError);
202
202
  });
203
203
 
204
204
  it('should include field information', () => {
205
- const error = new ValidationError('Email is invalid', 'email');
205
+ const error = new DataApiValidationError('Email is invalid', 'email');
206
206
 
207
207
  expect(error.field).toBe('email');
208
208
  expect(error.details).toHaveProperty('field', 'email');
@@ -214,7 +214,7 @@ describe('Error Classes', () => {
214
214
  { field: 'age', message: 'Must be a positive number' },
215
215
  ];
216
216
 
217
- const error = new ValidationError(
217
+ const error = new DataApiValidationError(
218
218
  'Validation failed',
219
219
  undefined,
220
220
  validationErrors
@@ -225,7 +225,7 @@ describe('Error Classes', () => {
225
225
  });
226
226
 
227
227
  it('should return empty array when no validation errors', () => {
228
- const error = new ValidationError('Validation failed');
228
+ const error = new DataApiValidationError('Validation failed');
229
229
 
230
230
  expect(error.getValidationErrors()).toEqual([]);
231
231
  });
@@ -286,7 +286,7 @@ describe('Error Helpers', () => {
286
286
  expect(error.code).toBe('NOT_FOUND');
287
287
  });
288
288
 
289
- it('should create ValidationError for 400 status', () => {
289
+ it('should create DataApiValidationError for 400 status', () => {
290
290
  const response = {
291
291
  status: 400,
292
292
  message: 'Bad request',
@@ -299,9 +299,9 @@ describe('Error Helpers', () => {
299
299
 
300
300
  const error = createErrorFromResponse(response);
301
301
 
302
- expect(error).toBeInstanceOf(ValidationError);
302
+ expect(error).toBeInstanceOf(DataApiValidationError);
303
303
  expect(error.statusCode).toBe(400);
304
- expect((error as ValidationError).validationErrors).toEqual([
304
+ expect((error as DataApiValidationError).validationErrors).toEqual([
305
305
  { field: 'email', message: 'Invalid email' },
306
306
  ]);
307
307
  });
@@ -379,7 +379,7 @@ describe('Error Helpers', () => {
379
379
  const bulkError = new BulkOperationError('create', 0, 1, []);
380
380
  const connError = new ConnectionError('timeout');
381
381
  const authError = new AuthenticationError();
382
- const validError = new ValidationError('invalid');
382
+ const validError = new DataApiValidationError('invalid');
383
383
 
384
384
  expect(isObjectStackError(metadataError)).toBe(true);
385
385
  expect(isObjectStackError(bulkError)).toBe(true);
@@ -409,7 +409,7 @@ describe('Error Helpers', () => {
409
409
 
410
410
  it('should return false for non-matching error type', () => {
411
411
  const error = new MetadataNotFoundError('users');
412
- expect(isErrorType(error, ValidationError)).toBe(false);
412
+ expect(isErrorType(error, DataApiValidationError)).toBe(false);
413
413
  });
414
414
 
415
415
  it('should return true for base class check', () => {
package/src/errors.ts CHANGED
@@ -159,12 +159,24 @@ export class AuthenticationError extends ObjectStackError {
159
159
  }
160
160
 
161
161
  /**
162
- * Error thrown when data validation fails
162
+ * Error thrown when the ObjectStack data API rejects a write as invalid.
163
+ *
164
+ * NOT the spec's `ValidationError` (`@objectstack/spec/kernel`), whose name this
165
+ * class wore until objectui#3160 (objectstack#4115 ledger batch 6). That one is
166
+ * a plain DATA SHAPE — `{ field, message, code? }`, one entry in a plugin
167
+ * manifest's validation report — and `@object-ui/types` re-exports it under that
168
+ * name. This is a runtime `Error` subclass carrying an HTTP status plus a list
169
+ * of such entries, so the two are not even the same KIND of thing.
170
+ *
171
+ * The name follows the convention registered on objectstack#4115 for this
172
+ * family — `<what was validated>Validation<Error|Result>`. `@object-ui/core`
173
+ * took `SchemaNodeValidationError` for its SDUI-tree walk; this one belongs to
174
+ * the data API.
163
175
  */
164
- export class ValidationError extends ObjectStackError {
176
+ export class DataApiValidationError extends ObjectStackError {
165
177
  /**
166
- * Create a new ValidationError
167
- *
178
+ * Create a new DataApiValidationError
179
+ *
168
180
  * @param message - Human-readable error message
169
181
  * @param field - The field that failed validation (optional)
170
182
  * @param validationErrors - Array of validation error details
@@ -186,6 +198,12 @@ export class ValidationError extends ObjectStackError {
186
198
  ...details,
187
199
  }
188
200
  );
201
+ // The RUNTIME name stays `'ValidationError'` on purpose — it is the wire
202
+ // discriminator this adapter shares with `@objectstack/client` and with
203
+ // consumers that sniff `err.name` rather than `instanceof`
204
+ // (`normaliseClientError` here, `@object-ui/react`'s `error-message`).
205
+ // Renaming the TypeScript symbol is a source-level rename; renaming this
206
+ // string would be a behaviour change nobody asked for.
189
207
  this.name = 'ValidationError';
190
208
  }
191
209
 
@@ -232,7 +250,7 @@ export function createErrorFromResponse(response: Record<string, unknown>, conte
232
250
  return new ObjectStackError(message, 'NOT_FOUND', 404, details);
233
251
 
234
252
  case 400:
235
- return new ValidationError(message, undefined, (response?.data as Record<string, unknown>)?.errors as Array<{ field: string; message: string }>, details);
253
+ return new DataApiValidationError(message, undefined, (response?.data as Record<string, unknown>)?.errors as Array<{ field: string; message: string }>, details);
236
254
 
237
255
  case 503:
238
256
  return new ConnectionError(message, (response?.config as Record<string, unknown>)?.url as string, details, 503);