@object-ui/data-objectstack 11.3.0 → 11.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # @object-ui/data-objectstack
2
2
 
3
+ ## 11.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1072701: Import wizard: use registered server-side import mappings (framework #2611). When an object has `mapping` metadata artifacts targeting it, the wizard shows a "Saved mapping" selector; picking one hands rename + transforms + write semantics to the server (the artifact is authoritative), replaces the manual column table with a read-only summary of the mapping, and submits `mappingName` over source-header rows (mutually exclusive with the inline column rename). `ImportRequestOptions` gains `mappingName`; the objectstack adapter gains `listImportMappings(objectName)` (feature-detected — the selector simply doesn't appear when unsupported). New `grid.import.*` strings added across all locales.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [9255686]
12
+ - Updated dependencies [1072701]
13
+ - @object-ui/types@11.5.0
14
+ - @object-ui/core@11.5.0
15
+
16
+ ## 11.4.0
17
+
18
+ ### Patch Changes
19
+
20
+ - c0164ad: fix(studio): surface spec-validation failures on the field at save/publish
21
+
22
+ When a Studio metadata draft failed spec validation, the designer got a single
23
+ opaque banner (and, on a partial publish, a false "published!" toast) — the
24
+ server was already returning field-anchored issues, but the client threw them
25
+ away. Two problems, both fixed:
26
+
27
+ - **`parseError` (data-objectstack)** read `String(body.error)`, which yields
28
+ `"[object Object]"` for the dispatcher's object-shaped error, and ignored the
29
+ validation `issues`. It now reads the message from either shape (string or
30
+ `{ message }`) and exposes `MetadataError.issues`, accepting all live server
31
+ shapes — top-level `body.issues` (REST server) and `error.details.issues`
32
+ (HTTP dispatcher).
33
+
34
+ - **Studio save/publish (app-shell)** now render those issues **field-anchored**.
35
+ A new `formatMetadataError` helper turns a caught error into one line per
36
+ offending field (`• fields.amount.type — Invalid option: …`); the save banners
37
+ render it with `whitespace-pre-line`. `doPublish` no longer claims success when
38
+ the response carries `data.failed[]` — it lists which drafts failed and why
39
+ (the server returns 200 with the failures buried, so the UI used to swallow
40
+ them). `formatPublishFailures` formats those per-draft.
41
+
42
+ Verified end-to-end against a live backend: an invalid object draft returns 422
43
+ with field-anchored issues, and the Studio banner shows
44
+ `• fields.amount.type — Invalid option: expected one of "text"|…` instead of a
45
+ generic message. Unit-tested: `parseError` on the dispatcher shape, and the
46
+ `formatMetadataError` / `formatPublishFailures` helpers.
47
+
48
+ - Updated dependencies [8bf6295]
49
+ - Updated dependencies [1948c5b]
50
+ - Updated dependencies [c38d107]
51
+ - @object-ui/types@11.4.0
52
+ - @object-ui/core@11.4.0
53
+
3
54
  ## 11.3.0
4
55
 
5
56
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -808,13 +808,17 @@ async function parseError(res) {
808
808
  } catch {
809
809
  body = await res.text().catch(() => void 0);
810
810
  }
811
- const message = (body && typeof body === "object" && "error" in body ? String(body.error) : void 0) ?? `Metadata request failed: ${res.status} ${res.statusText}`;
812
- const err = new Error(message);
811
+ const bodyObj = body && typeof body === "object" ? body : void 0;
812
+ const errNode = bodyObj?.error;
813
+ const message = (errNode && typeof errNode === "object" ? errNode.message : errNode) ?? bodyObj?.message ?? `Metadata request failed: ${res.status} ${res.statusText}`;
814
+ const err = new Error(String(message));
813
815
  err.status = res.status;
814
816
  err.body = body;
815
- if (body && typeof body === "object" && "code" in body) {
816
- err.code = String(body.code);
817
- }
817
+ const details = errNode && typeof errNode === "object" ? errNode.details : void 0;
818
+ const issues = details?.issues ?? (errNode && typeof errNode === "object" ? errNode.issues : void 0) ?? bodyObj?.issues;
819
+ if (Array.isArray(issues)) err.issues = issues;
820
+ const code = details?.code ?? (errNode && typeof errNode === "object" ? errNode.code : void 0) ?? bodyObj?.code;
821
+ if (code != null) err.code = String(code);
818
822
  return err;
819
823
  }
820
824
  var MetadataClient = class _MetadataClient {
@@ -1524,6 +1528,11 @@ var ObjectStackAdapter = class {
1524
1528
  // so optional collections like sys_presence don't hammer the server with
1525
1529
  // failing requests on every record open / panel render.
1526
1530
  __publicField(this, "missingResources", /* @__PURE__ */ new Set());
1531
+ // Subscribers registered via onMutation(). Emitted after each successful
1532
+ // create/update/delete so data-bound views (ListView, ObjectView, kanban,
1533
+ // calendar) auto-refresh — the interface ListView relies on to reflect
1534
+ // inline-edit "Save All" writes without a manual reload.
1535
+ __publicField(this, "mutationListeners", /* @__PURE__ */ new Set());
1527
1536
  this.client = new import_client.ObjectStackClient({ ...config, logger: createQuietHttpLogger() });
1528
1537
  this.metadataCache = new MetadataCache(config.cache);
1529
1538
  this.autoReconnect = config.autoReconnect ?? true;
@@ -1732,9 +1741,35 @@ var ObjectStackAdapter = class {
1732
1741
  /**
1733
1742
  * Create a new record.
1734
1743
  */
1744
+ /**
1745
+ * Notify all mutation subscribers. A throwing listener must not break the
1746
+ * mutation or starve the other subscribers, so each is isolated.
1747
+ */
1748
+ emitMutation(event) {
1749
+ for (const listener of this.mutationListeners) {
1750
+ try {
1751
+ listener(event);
1752
+ } catch (err) {
1753
+ console.warn("ObjectStackAdapter: mutation listener error", err);
1754
+ }
1755
+ }
1756
+ }
1757
+ /**
1758
+ * Subscribe to create/update/delete events on any resource. Returns an
1759
+ * unsubscribe function. Data-bound views use this to auto-refresh after a
1760
+ * mutation (e.g. inline-edit "Save All", which writes through `update` and
1761
+ * must repaint the list without a manual reload).
1762
+ */
1763
+ onMutation(callback) {
1764
+ this.mutationListeners.add(callback);
1765
+ return () => {
1766
+ this.mutationListeners.delete(callback);
1767
+ };
1768
+ }
1735
1769
  async create(resource, data) {
1736
1770
  await this.connect();
1737
1771
  const result = await this.client.data.create(resource, data);
1772
+ this.emitMutation({ type: "create", resource, record: { ...result.record } });
1738
1773
  return result.record;
1739
1774
  }
1740
1775
  /**
@@ -1757,6 +1792,7 @@ var ObjectStackAdapter = class {
1757
1792
  data,
1758
1793
  opts?.ifMatch ? { ifMatch: opts.ifMatch } : void 0
1759
1794
  );
1795
+ this.emitMutation({ type: "update", resource, id, record: { ...result.record } });
1760
1796
  return result.record;
1761
1797
  } catch (err) {
1762
1798
  throw normaliseClientError(err);
@@ -1777,6 +1813,9 @@ var ObjectStackAdapter = class {
1777
1813
  String(id),
1778
1814
  opts?.ifMatch ? { ifMatch: opts.ifMatch } : void 0
1779
1815
  );
1816
+ if (result.deleted) {
1817
+ this.emitMutation({ type: "delete", resource, id });
1818
+ }
1780
1819
  return result.deleted;
1781
1820
  } catch (err) {
1782
1821
  throw normaliseClientError(err);
@@ -1801,15 +1840,19 @@ var ObjectStackAdapter = class {
1801
1840
  await this.connect();
1802
1841
  if (!ids || ids.length === 0) return 0;
1803
1842
  const records = ids.map((id) => ({ id: String(id), data: patch }));
1843
+ const emitBulk = (count) => {
1844
+ if (count > 0) this.emitMutation({ type: "update", resource });
1845
+ return count;
1846
+ };
1804
1847
  const updateMany = this.client.data.updateMany;
1805
1848
  if (typeof updateMany === "function") {
1806
1849
  try {
1807
1850
  const res = await updateMany(resource, records, { continueOnError: true });
1808
1851
  if (res && typeof res === "object" && typeof res.succeeded === "number") {
1809
- return res.succeeded;
1852
+ return emitBulk(res.succeeded);
1810
1853
  }
1811
- if (Array.isArray(res)) return res.length;
1812
- return ids.length;
1854
+ if (Array.isArray(res)) return emitBulk(res.length);
1855
+ return emitBulk(ids.length);
1813
1856
  } catch (err) {
1814
1857
  throw normaliseClientError(err);
1815
1858
  }
@@ -1822,7 +1865,7 @@ var ObjectStackAdapter = class {
1822
1865
  } catch {
1823
1866
  }
1824
1867
  }
1825
- return succeeded;
1868
+ return emitBulk(succeeded);
1826
1869
  }
1827
1870
  /**
1828
1871
  * Single-call bulk delete. Mirrors the bulkUpdate contract: prefers
@@ -1835,15 +1878,19 @@ var ObjectStackAdapter = class {
1835
1878
  await this.connect();
1836
1879
  if (!ids || ids.length === 0) return 0;
1837
1880
  const strIds = ids.map((id) => String(id));
1881
+ const emitBulk = (count) => {
1882
+ if (count > 0) this.emitMutation({ type: "delete", resource });
1883
+ return count;
1884
+ };
1838
1885
  const deleteMany = this.client.data.deleteMany;
1839
1886
  if (typeof deleteMany === "function") {
1840
1887
  try {
1841
1888
  const res = await deleteMany(resource, strIds, { continueOnError: true });
1842
1889
  if (res && typeof res === "object" && typeof res.succeeded === "number") {
1843
- return res.succeeded;
1890
+ return emitBulk(res.succeeded);
1844
1891
  }
1845
- if (Array.isArray(res)) return res.length;
1846
- return strIds.length;
1892
+ if (Array.isArray(res)) return emitBulk(res.length);
1893
+ return emitBulk(strIds.length);
1847
1894
  } catch (err) {
1848
1895
  throw normaliseClientError(err);
1849
1896
  }
@@ -1856,7 +1903,7 @@ var ObjectStackAdapter = class {
1856
1903
  } catch {
1857
1904
  }
1858
1905
  }
1859
- return succeeded;
1906
+ return emitBulk(succeeded);
1860
1907
  }
1861
1908
  /**
1862
1909
  * Bulk operations with optimized batch processing and error handling.
@@ -2016,6 +2063,157 @@ var ObjectStackAdapter = class {
2016
2063
  );
2017
2064
  }
2018
2065
  }
2066
+ /**
2067
+ * Bulk-import raw spreadsheet rows in a single server round-trip via
2068
+ * `POST /api/v1/data/:object/import`. The server performs all value coercion
2069
+ * (booleans, numbers, dates→ISO, select label→code, lookup name→id) from the
2070
+ * object's field metadata, so this method forwards the request verbatim and
2071
+ * returns the aggregate + per-row result untouched.
2072
+ *
2073
+ * Requires `@objectstack/client` with `data.import` (server `/import` route).
2074
+ * Callers should feature-detect (`typeof dataSource.importRecords`) and fall
2075
+ * back to a per-row `create` loop when unavailable.
2076
+ */
2077
+ async importRecords(resource, request) {
2078
+ await this.connect();
2079
+ const importFn = this.client.data.import;
2080
+ if (typeof importFn !== "function") {
2081
+ throw new ObjectStackError(
2082
+ "The connected @objectstack/client does not support data.import(). Upgrade the client, or import via a per-row create fallback.",
2083
+ "UNSUPPORTED_OPERATION",
2084
+ 400
2085
+ );
2086
+ }
2087
+ try {
2088
+ const result = await importFn.call(this.client.data, resource, request);
2089
+ return result;
2090
+ } catch (err) {
2091
+ throw normaliseClientError(err);
2092
+ }
2093
+ }
2094
+ /**
2095
+ * Feature-detect the async import-job API on the connected client. Older
2096
+ * clients/servers lack these routes; callers fall back to {@link importRecords}.
2097
+ */
2098
+ importJobApi() {
2099
+ const d = this.client.data;
2100
+ if (typeof d.createImportJob !== "function") return void 0;
2101
+ return d;
2102
+ }
2103
+ /**
2104
+ * Start an asynchronous import job — the large-file counterpart to
2105
+ * {@link importRecords}. Posts the whole payload once; the server processes
2106
+ * rows in the background. Requires an `@objectstack/client` new enough to
2107
+ * expose `data.createImportJob` (server `/import/jobs` route). Callers should
2108
+ * feature-detect (`typeof dataSource.createImportJob`) and fall back to the
2109
+ * synchronous path when unavailable.
2110
+ */
2111
+ async createImportJob(resource, request) {
2112
+ await this.connect();
2113
+ const api = this.importJobApi();
2114
+ if (!api) {
2115
+ throw new ObjectStackError(
2116
+ "The connected @objectstack/client does not support async import jobs (data.createImportJob). Upgrade the client, or use the synchronous importRecords() path.",
2117
+ "UNSUPPORTED_OPERATION",
2118
+ 400
2119
+ );
2120
+ }
2121
+ try {
2122
+ return await api.createImportJob.call(this.client.data, resource, request);
2123
+ } catch (err) {
2124
+ throw normaliseClientError(err);
2125
+ }
2126
+ }
2127
+ /** Poll an import job's progress. Requires {@link createImportJob} support. */
2128
+ async getImportJobProgress(jobId) {
2129
+ await this.connect();
2130
+ const api = this.importJobApi();
2131
+ if (!api) {
2132
+ throw new ObjectStackError(
2133
+ "The connected @objectstack/client does not support async import jobs.",
2134
+ "UNSUPPORTED_OPERATION",
2135
+ 400
2136
+ );
2137
+ }
2138
+ try {
2139
+ return await api.getImportJobProgress.call(this.client.data, jobId);
2140
+ } catch (err) {
2141
+ throw normaliseClientError(err);
2142
+ }
2143
+ }
2144
+ /** Fetch an import job's capped per-row results. */
2145
+ async getImportJobResults(jobId) {
2146
+ await this.connect();
2147
+ const api = this.importJobApi();
2148
+ if (!api) {
2149
+ throw new ObjectStackError(
2150
+ "The connected @objectstack/client does not support async import jobs.",
2151
+ "UNSUPPORTED_OPERATION",
2152
+ 400
2153
+ );
2154
+ }
2155
+ try {
2156
+ return await api.getImportJobResults.call(this.client.data, jobId);
2157
+ } catch (err) {
2158
+ throw normaliseClientError(err);
2159
+ }
2160
+ }
2161
+ /** List recent import jobs (history), newest first. */
2162
+ async listImportJobs(options = {}) {
2163
+ await this.connect();
2164
+ const api = this.importJobApi();
2165
+ if (!api) {
2166
+ throw new ObjectStackError(
2167
+ "The connected @objectstack/client does not support async import jobs.",
2168
+ "UNSUPPORTED_OPERATION",
2169
+ 400
2170
+ );
2171
+ }
2172
+ try {
2173
+ return await api.listImportJobs.call(this.client.data, options);
2174
+ } catch (err) {
2175
+ throw normaliseClientError(err);
2176
+ }
2177
+ }
2178
+ /** Cancel a pending/running import job (cooperative). */
2179
+ async cancelImportJob(jobId) {
2180
+ await this.connect();
2181
+ const api = this.importJobApi();
2182
+ if (!api) {
2183
+ throw new ObjectStackError(
2184
+ "The connected @objectstack/client does not support async import jobs.",
2185
+ "UNSUPPORTED_OPERATION",
2186
+ 400
2187
+ );
2188
+ }
2189
+ try {
2190
+ await api.cancelImportJob.call(this.client.data, jobId);
2191
+ } catch (err) {
2192
+ throw normaliseClientError(err);
2193
+ }
2194
+ }
2195
+ /**
2196
+ * Logically roll back a finished import job — delete the records it created
2197
+ * and restore the records it updated to their pre-import values. Requires an
2198
+ * `@objectstack/client` new enough to expose `data.undoImportJob`, and a job
2199
+ * the server captured an undo log for (see {@link ImportJobProgressInfo.undoable}).
2200
+ */
2201
+ async undoImportJob(jobId) {
2202
+ await this.connect();
2203
+ const api = this.importJobApi();
2204
+ if (!api || typeof api.undoImportJob !== "function") {
2205
+ throw new ObjectStackError(
2206
+ "The connected @objectstack/client does not support undoing import jobs (data.undoImportJob).",
2207
+ "UNSUPPORTED_OPERATION",
2208
+ 400
2209
+ );
2210
+ }
2211
+ try {
2212
+ return await api.undoImportJob.call(this.client.data, jobId);
2213
+ } catch (err) {
2214
+ throw normaliseClientError(err);
2215
+ }
2216
+ }
2019
2217
  /**
2020
2218
  * Normalize the result from data.find() or data.query() into a consistent QueryResult.
2021
2219
  */
@@ -2447,6 +2645,25 @@ var ObjectStackAdapter = class {
2447
2645
  return [];
2448
2646
  }
2449
2647
  }
2648
+ /**
2649
+ * List registered import `mapping` artifacts targeting a given object
2650
+ * (framework #2611). Reads the `mapping` metadata kind via the overlay API
2651
+ * and filters by `targetObject` client-side (the metadata index is
2652
+ * name-only). Feeds the import wizard's "saved mapping" selector; a failure
2653
+ * (older server without the `mapping` kind) degrades to an empty list, so
2654
+ * the selector simply doesn't appear.
2655
+ */
2656
+ async listImportMappings(objectName) {
2657
+ await this.connect();
2658
+ try {
2659
+ const result = await this.client.meta.getItems("mapping");
2660
+ const items = Array.isArray(result?.items) ? result.items : Array.isArray(result) ? result : [];
2661
+ return items.filter((m) => m && m.targetObject === objectName);
2662
+ } catch (err) {
2663
+ console.warn("[OBJECTSTACKDataSource] listImportMappings failed:", err);
2664
+ return [];
2665
+ }
2666
+ }
2450
2667
  /**
2451
2668
  * Create a new overlay view for an object. The view's `name` is the
2452
2669
  * stable identifier — must be unique within the project scope. Returns
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ObjectStackClient } from '@objectstack/client';
2
- import { DataSource, QueryParams, QueryResult, ExportDownloadRequest, FileUploadResult } from '@object-ui/types';
2
+ import { DataSource, QueryParams, QueryResult, MutationEvent, ImportRequestOptions, ImportRecordsResult, CreateImportJobResult, ImportJobProgressInfo, ImportJobResultsInfo, ListImportJobsOptions, ImportJobSummaryInfo, ImportJobUndoResult, ExportDownloadRequest, FileUploadResult } from '@object-ui/types';
3
3
  export { FileUploadResult } from '@object-ui/types';
4
4
 
5
5
  /**
@@ -764,10 +764,19 @@ interface MetadataHistoryOptions {
764
764
  /** Limit the number of events returned. */
765
765
  limit?: number;
766
766
  }
767
+ /** A single field-anchored spec-validation issue (server `error.details.issues`). */
768
+ interface MetadataValidationIssue {
769
+ /** Dot-path to the offending field, e.g. `fields.amount.type` (`''` = root). */
770
+ path: string;
771
+ message: string;
772
+ code?: string;
773
+ }
767
774
  interface MetadataError extends Error {
768
775
  status: number;
769
776
  code?: string;
770
777
  body?: unknown;
778
+ /** Structured spec-validation issues, when the failure was a validation error. */
779
+ issues?: MetadataValidationIssue[];
771
780
  }
772
781
  /**
773
782
  * MetadataClient — read/write protocol metadata via the framework REST API.
@@ -1302,6 +1311,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1302
1311
  private fetchImpl;
1303
1312
  private inflightFinds;
1304
1313
  private missingResources;
1314
+ private mutationListeners;
1305
1315
  constructor(config: {
1306
1316
  baseUrl: string;
1307
1317
  token?: string;
@@ -1359,6 +1369,18 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1359
1369
  /**
1360
1370
  * Create a new record.
1361
1371
  */
1372
+ /**
1373
+ * Notify all mutation subscribers. A throwing listener must not break the
1374
+ * mutation or starve the other subscribers, so each is isolated.
1375
+ */
1376
+ private emitMutation;
1377
+ /**
1378
+ * Subscribe to create/update/delete events on any resource. Returns an
1379
+ * unsubscribe function. Data-bound views use this to auto-refresh after a
1380
+ * mutation (e.g. inline-edit "Save All", which writes through `update` and
1381
+ * must repaint the list without a manual reload).
1382
+ */
1383
+ onMutation(callback: (event: MutationEvent<T>) => void): () => void;
1362
1384
  create(resource: string, data: Partial<T>): Promise<T>;
1363
1385
  /**
1364
1386
  * Update an existing record.
@@ -1432,6 +1454,47 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1432
1454
  results: any[];
1433
1455
  }>;
1434
1456
  bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]>;
1457
+ /**
1458
+ * Bulk-import raw spreadsheet rows in a single server round-trip via
1459
+ * `POST /api/v1/data/:object/import`. The server performs all value coercion
1460
+ * (booleans, numbers, dates→ISO, select label→code, lookup name→id) from the
1461
+ * object's field metadata, so this method forwards the request verbatim and
1462
+ * returns the aggregate + per-row result untouched.
1463
+ *
1464
+ * Requires `@objectstack/client` with `data.import` (server `/import` route).
1465
+ * Callers should feature-detect (`typeof dataSource.importRecords`) and fall
1466
+ * back to a per-row `create` loop when unavailable.
1467
+ */
1468
+ importRecords(resource: string, request: ImportRequestOptions): Promise<ImportRecordsResult>;
1469
+ /**
1470
+ * Feature-detect the async import-job API on the connected client. Older
1471
+ * clients/servers lack these routes; callers fall back to {@link importRecords}.
1472
+ */
1473
+ private importJobApi;
1474
+ /**
1475
+ * Start an asynchronous import job — the large-file counterpart to
1476
+ * {@link importRecords}. Posts the whole payload once; the server processes
1477
+ * rows in the background. Requires an `@objectstack/client` new enough to
1478
+ * expose `data.createImportJob` (server `/import/jobs` route). Callers should
1479
+ * feature-detect (`typeof dataSource.createImportJob`) and fall back to the
1480
+ * synchronous path when unavailable.
1481
+ */
1482
+ createImportJob(resource: string, request: ImportRequestOptions): Promise<CreateImportJobResult>;
1483
+ /** Poll an import job's progress. Requires {@link createImportJob} support. */
1484
+ getImportJobProgress(jobId: string): Promise<ImportJobProgressInfo>;
1485
+ /** Fetch an import job's capped per-row results. */
1486
+ getImportJobResults(jobId: string): Promise<ImportJobResultsInfo>;
1487
+ /** List recent import jobs (history), newest first. */
1488
+ listImportJobs(options?: ListImportJobsOptions): Promise<ImportJobSummaryInfo[]>;
1489
+ /** Cancel a pending/running import job (cooperative). */
1490
+ cancelImportJob(jobId: string): Promise<void>;
1491
+ /**
1492
+ * Logically roll back a finished import job — delete the records it created
1493
+ * and restore the records it updated to their pre-import values. Requires an
1494
+ * `@objectstack/client` new enough to expose `data.undoImportJob`, and a job
1495
+ * the server captured an undo log for (see {@link ImportJobProgressInfo.undoable}).
1496
+ */
1497
+ undoImportJob(jobId: string): Promise<ImportJobUndoResult>;
1435
1498
  /**
1436
1499
  * Normalize the result from data.find() or data.query() into a consistent QueryResult.
1437
1500
  */
@@ -1558,6 +1621,15 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1558
1621
  * client-side because the metadata index is name-only, not field-typed.
1559
1622
  */
1560
1623
  listViews(objectName: string): Promise<any[]>;
1624
+ /**
1625
+ * List registered import `mapping` artifacts targeting a given object
1626
+ * (framework #2611). Reads the `mapping` metadata kind via the overlay API
1627
+ * and filters by `targetObject` client-side (the metadata index is
1628
+ * name-only). Feeds the import wizard's "saved mapping" selector; a failure
1629
+ * (older server without the `mapping` kind) degrades to an empty list, so
1630
+ * the selector simply doesn't appear.
1631
+ */
1632
+ listImportMappings(objectName: string): Promise<any[]>;
1561
1633
  /**
1562
1634
  * Create a new overlay view for an object. The view's `name` is the
1563
1635
  * stable identifier — must be unique within the project scope. Returns
@@ -1781,4 +1853,4 @@ declare function createObjectStackAdapter<T = unknown>(config: {
1781
1853
  reconnectDelay?: number;
1782
1854
  }): DataSource<T>;
1783
1855
 
1784
- export { 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 EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, 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, 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, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isConcurrentUpdateError, isErrorType, isObjectStackError, normaliseClientError, snapToGrid, validatePluginContract };
1856
+ export { 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 EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, 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, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isConcurrentUpdateError, isErrorType, isObjectStackError, normaliseClientError, snapToGrid, validatePluginContract };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ObjectStackClient } from '@objectstack/client';
2
- import { DataSource, QueryParams, QueryResult, ExportDownloadRequest, FileUploadResult } from '@object-ui/types';
2
+ import { DataSource, QueryParams, QueryResult, MutationEvent, ImportRequestOptions, ImportRecordsResult, CreateImportJobResult, ImportJobProgressInfo, ImportJobResultsInfo, ListImportJobsOptions, ImportJobSummaryInfo, ImportJobUndoResult, ExportDownloadRequest, FileUploadResult } from '@object-ui/types';
3
3
  export { FileUploadResult } from '@object-ui/types';
4
4
 
5
5
  /**
@@ -764,10 +764,19 @@ interface MetadataHistoryOptions {
764
764
  /** Limit the number of events returned. */
765
765
  limit?: number;
766
766
  }
767
+ /** A single field-anchored spec-validation issue (server `error.details.issues`). */
768
+ interface MetadataValidationIssue {
769
+ /** Dot-path to the offending field, e.g. `fields.amount.type` (`''` = root). */
770
+ path: string;
771
+ message: string;
772
+ code?: string;
773
+ }
767
774
  interface MetadataError extends Error {
768
775
  status: number;
769
776
  code?: string;
770
777
  body?: unknown;
778
+ /** Structured spec-validation issues, when the failure was a validation error. */
779
+ issues?: MetadataValidationIssue[];
771
780
  }
772
781
  /**
773
782
  * MetadataClient — read/write protocol metadata via the framework REST API.
@@ -1302,6 +1311,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1302
1311
  private fetchImpl;
1303
1312
  private inflightFinds;
1304
1313
  private missingResources;
1314
+ private mutationListeners;
1305
1315
  constructor(config: {
1306
1316
  baseUrl: string;
1307
1317
  token?: string;
@@ -1359,6 +1369,18 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1359
1369
  /**
1360
1370
  * Create a new record.
1361
1371
  */
1372
+ /**
1373
+ * Notify all mutation subscribers. A throwing listener must not break the
1374
+ * mutation or starve the other subscribers, so each is isolated.
1375
+ */
1376
+ private emitMutation;
1377
+ /**
1378
+ * Subscribe to create/update/delete events on any resource. Returns an
1379
+ * unsubscribe function. Data-bound views use this to auto-refresh after a
1380
+ * mutation (e.g. inline-edit "Save All", which writes through `update` and
1381
+ * must repaint the list without a manual reload).
1382
+ */
1383
+ onMutation(callback: (event: MutationEvent<T>) => void): () => void;
1362
1384
  create(resource: string, data: Partial<T>): Promise<T>;
1363
1385
  /**
1364
1386
  * Update an existing record.
@@ -1432,6 +1454,47 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1432
1454
  results: any[];
1433
1455
  }>;
1434
1456
  bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]>;
1457
+ /**
1458
+ * Bulk-import raw spreadsheet rows in a single server round-trip via
1459
+ * `POST /api/v1/data/:object/import`. The server performs all value coercion
1460
+ * (booleans, numbers, dates→ISO, select label→code, lookup name→id) from the
1461
+ * object's field metadata, so this method forwards the request verbatim and
1462
+ * returns the aggregate + per-row result untouched.
1463
+ *
1464
+ * Requires `@objectstack/client` with `data.import` (server `/import` route).
1465
+ * Callers should feature-detect (`typeof dataSource.importRecords`) and fall
1466
+ * back to a per-row `create` loop when unavailable.
1467
+ */
1468
+ importRecords(resource: string, request: ImportRequestOptions): Promise<ImportRecordsResult>;
1469
+ /**
1470
+ * Feature-detect the async import-job API on the connected client. Older
1471
+ * clients/servers lack these routes; callers fall back to {@link importRecords}.
1472
+ */
1473
+ private importJobApi;
1474
+ /**
1475
+ * Start an asynchronous import job — the large-file counterpart to
1476
+ * {@link importRecords}. Posts the whole payload once; the server processes
1477
+ * rows in the background. Requires an `@objectstack/client` new enough to
1478
+ * expose `data.createImportJob` (server `/import/jobs` route). Callers should
1479
+ * feature-detect (`typeof dataSource.createImportJob`) and fall back to the
1480
+ * synchronous path when unavailable.
1481
+ */
1482
+ createImportJob(resource: string, request: ImportRequestOptions): Promise<CreateImportJobResult>;
1483
+ /** Poll an import job's progress. Requires {@link createImportJob} support. */
1484
+ getImportJobProgress(jobId: string): Promise<ImportJobProgressInfo>;
1485
+ /** Fetch an import job's capped per-row results. */
1486
+ getImportJobResults(jobId: string): Promise<ImportJobResultsInfo>;
1487
+ /** List recent import jobs (history), newest first. */
1488
+ listImportJobs(options?: ListImportJobsOptions): Promise<ImportJobSummaryInfo[]>;
1489
+ /** Cancel a pending/running import job (cooperative). */
1490
+ cancelImportJob(jobId: string): Promise<void>;
1491
+ /**
1492
+ * Logically roll back a finished import job — delete the records it created
1493
+ * and restore the records it updated to their pre-import values. Requires an
1494
+ * `@objectstack/client` new enough to expose `data.undoImportJob`, and a job
1495
+ * the server captured an undo log for (see {@link ImportJobProgressInfo.undoable}).
1496
+ */
1497
+ undoImportJob(jobId: string): Promise<ImportJobUndoResult>;
1435
1498
  /**
1436
1499
  * Normalize the result from data.find() or data.query() into a consistent QueryResult.
1437
1500
  */
@@ -1558,6 +1621,15 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1558
1621
  * client-side because the metadata index is name-only, not field-typed.
1559
1622
  */
1560
1623
  listViews(objectName: string): Promise<any[]>;
1624
+ /**
1625
+ * List registered import `mapping` artifacts targeting a given object
1626
+ * (framework #2611). Reads the `mapping` metadata kind via the overlay API
1627
+ * and filters by `targetObject` client-side (the metadata index is
1628
+ * name-only). Feeds the import wizard's "saved mapping" selector; a failure
1629
+ * (older server without the `mapping` kind) degrades to an empty list, so
1630
+ * the selector simply doesn't appear.
1631
+ */
1632
+ listImportMappings(objectName: string): Promise<any[]>;
1561
1633
  /**
1562
1634
  * Create a new overlay view for an object. The view's `name` is the
1563
1635
  * stable identifier — must be unique within the project scope. Returns
@@ -1781,4 +1853,4 @@ declare function createObjectStackAdapter<T = unknown>(config: {
1781
1853
  reconnectDelay?: number;
1782
1854
  }): DataSource<T>;
1783
1855
 
1784
- export { 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 EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, 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, 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, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isConcurrentUpdateError, isErrorType, isObjectStackError, normaliseClientError, snapToGrid, validatePluginContract };
1856
+ export { 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 EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, 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, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isConcurrentUpdateError, isErrorType, isObjectStackError, normaliseClientError, snapToGrid, validatePluginContract };