@object-ui/data-objectstack 17.5.0 → 17.7.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 +2311 -0
- package/README.md +557 -11
- package/dist/index.cjs +1663 -463
- package/dist/index.d.cts +1780 -518
- package/dist/index.d.ts +1780 -518
- package/dist/index.js +1648 -457
- package/package.json +9 -9
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/aggregate-capability.test.ts +0 -177
- package/src/apiAccessDenied.test.ts +0 -233
- package/src/appAccessProbe.test.ts +0 -147
- package/src/bulkDelete.test.ts +0 -75
- package/src/bulkUpdate.test.ts +0 -93
- package/src/cache/MetadataCache.test.ts +0 -477
- package/src/cache/MetadataCache.ts +0 -289
- package/src/cloud-surface-retired-4152.pin.test.ts +0 -171
- package/src/connection.test.ts +0 -188
- package/src/contracts.ts +0 -115
- package/src/deleteView.homes.test.ts +0 -389
- package/src/deleteViewContract.types.test.ts +0 -246
- package/src/errors.test.ts +0 -426
- package/src/errors.ts +0 -293
- package/src/exportDownload.test.ts +0 -141
- package/src/filter-entry-translation.test.ts +0 -360
- package/src/filter-operator-ast-parity.test.ts +0 -135
- package/src/getObjectSchema.test.ts +0 -145
- package/src/httpFailureLogging.test.ts +0 -109
- package/src/importJob.test.ts +0 -71
- package/src/index.ts +0 -4442
- package/src/integration.ts +0 -200
- package/src/listViewOverrides.test.ts +0 -241
- package/src/listViews.test.ts +0 -175
- package/src/metadata-client.get-envelope.test.ts +0 -171
- package/src/metadata-client.saveAdvisories.test.ts +0 -226
- package/src/metadata-client.test.ts +0 -303
- package/src/metadata-client.ts +0 -1046
- package/src/occ.test.ts +0 -101
- package/src/onMutation.test.ts +0 -589
- package/src/onSaveAdvisory.test.ts +0 -311
- package/src/onWriteWarning.test.ts +0 -273
- package/src/orderby-serialization.test.ts +0 -115
- package/src/queryDataset.test.ts +0 -543
- package/src/searchAll.test.ts +0 -120
- package/src/security.ts +0 -243
- package/src/spec-symbol-batch6.test.ts +0 -326
- package/src/studio.ts +0 -152
- package/src/updateView.draft.test.ts +0 -242
- package/src/userState.test.ts +0 -315
- package/src/userState.ts +0 -238
- package/src/v3-compat.test.ts +0 -229
- package/src/validation-error.test.ts +0 -94
- package/src/viewCacheInvalidation.pin.test.ts +0 -260
- package/src/viewInvalidationSeam.pin.test.ts +0 -162
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ObjectStackClient } from '@objectstack/client';
|
|
2
|
-
import { DroppedFieldsEvent } from '@objectstack/spec/data';
|
|
2
|
+
import { DroppedFieldsEvent, EngineAggregateOptions } from '@objectstack/spec/data';
|
|
3
3
|
export { DroppedFieldsEvent } from '@objectstack/spec/data';
|
|
4
|
-
import {
|
|
4
|
+
import { ListViewGroupHeaderRow } from '@objectstack/spec/ui';
|
|
5
|
+
import { GetMetaItemLayeredResponse, RuntimeAuthoringIssue, PublishPackageDraftsResponse } from '@objectstack/spec/api';
|
|
5
6
|
export { RuntimeAuthoringIssue } from '@objectstack/spec/api';
|
|
6
7
|
import { DatasetSelection, AnalyticsResult } from '@objectstack/spec/contracts';
|
|
7
8
|
import { DataSource, QueryParams, QueryResult, GlobalSearchResult, DataSourceMutationEvent, BatchTransactionOperation, ImportRequestOptions, ImportRecordsResult, CreateImportJobResult, ImportJobProgressInfo, ImportJobResultsInfo, ListImportJobsOptions, ImportJobSummaryInfo, ImportJobUndoResult, ExportDownloadRequest, DeleteViewResult, FileUploadResult } from '@object-ui/types';
|
|
@@ -15,42 +16,41 @@ import { DatasetDrillRange } from '@object-ui/core';
|
|
|
15
16
|
* This source code is licensed under the MIT license found in the
|
|
16
17
|
* LICENSE file in the root directory of this source tree.
|
|
17
18
|
*/
|
|
18
|
-
/**
|
|
19
|
-
* MetadataClient
|
|
20
|
-
*
|
|
21
|
-
* Thin, framework-agnostic HTTP client for the ObjectStack metadata
|
|
22
|
-
* API (`/api/v1/meta/*`). Used by the platform Setup app surfaces in
|
|
23
|
-
* `@object-ui/plugin-designer` (Object Manager, Field Designer, etc.)
|
|
24
|
-
* so they can read and write protocol metadata without depending on
|
|
25
|
-
* `ObjectStackAdapter` (which is a generic data-source binding and
|
|
26
|
-
* carries a lot of view/dashboard-specific behaviour we don't need
|
|
27
|
-
* here).
|
|
28
|
-
*
|
|
29
|
-
* Endpoints (see `packages/rest/src/rest-server.ts`):
|
|
30
|
-
* GET /api/v1/meta — list all metadata types
|
|
31
|
-
* GET /api/v1/meta/:type — list items of a type
|
|
32
|
-
* GET /api/v1/meta/:type/:name — get one item (returns the
|
|
33
|
-
* unwrapped item content)
|
|
34
|
-
* PUT /api/v1/meta/:type/:name — save (overlay) one item
|
|
35
|
-
* honours `If-Match` for OCC
|
|
36
|
-
* DELETE /api/v1/meta/:type/:name — reset overlay to artifact
|
|
37
|
-
* GET /api/v1/meta/:type/:name/history — durable change log
|
|
38
|
-
*
|
|
39
|
-
* The client deliberately keeps the response shape opaque (typed as
|
|
40
|
-
* `unknown`/generic `T`) so callers explicitly narrow per-metadata-
|
|
41
|
-
* type, mirroring the framework's "single Zod source per type" rule.
|
|
42
|
-
*/
|
|
43
19
|
|
|
44
20
|
/**
|
|
45
|
-
* Emitted after a
|
|
46
|
-
*
|
|
47
|
-
*
|
|
21
|
+
* Emitted after a metadata WRITE whose response carried a non-empty
|
|
22
|
+
* `advisories` array. The write SUCCEEDED — the row persisted and the server
|
|
23
|
+
* returned 200 — so this is advisory, never a failure.
|
|
48
24
|
*
|
|
49
25
|
* Deliberately the same shape of seam as `ObjectStackAdapter.onWriteWarning`
|
|
50
|
-
* (#3431
|
|
26
|
+
* (objectstack-ai/objectstack#3431 / objectstack-ai/objectstack#3455): a successful write whose response carries something the author
|
|
51
27
|
* needs to be told, surfaced to the shell as an event so the data layer never
|
|
52
28
|
* imports a toaster. The difference is only which door produced it — that one
|
|
53
|
-
* is record CRUD,
|
|
29
|
+
* is record CRUD, these are the metadata write doors.
|
|
30
|
+
*
|
|
31
|
+
* ## Both write doors report (#5026)
|
|
32
|
+
*
|
|
33
|
+
* The objectstack-ai/objectstack#4463 runtime authoring gate runs on BOTH metadata write doors by its D1
|
|
34
|
+
* ruling — a draft→active promotion is gated exactly as a direct active save —
|
|
35
|
+
* and its non-blocking findings ride the 2xx of whichever door earned them.
|
|
36
|
+
* `SaveMetaItemResponseSchema` has carried the key since objectstack-ai/objectstack#4717 and
|
|
37
|
+
* `PublishMetaItemResponseSchema` since objectstack#9176; both declare it at
|
|
38
|
+
* the response's TOP level, under the same name and with the same
|
|
39
|
+
* `RuntimeAuthoringIssueSchema` element type, which is what lets one reader and
|
|
40
|
+
* one event serve both. Measured against the installed `@objectstack/spec`
|
|
41
|
+
* rather than assumed — see the PR for the probe.
|
|
42
|
+
*
|
|
43
|
+
* ## And the BATCH publish door (`ce986aafc`)
|
|
44
|
+
*
|
|
45
|
+
* `PublishPackageDraftsResponseSchema` declares the same key with the same
|
|
46
|
+
* element type, riding EACH `published[]` element rather than a parallel
|
|
47
|
+
* top-level map (objectstack#9343's ruled shape). So the reader is still one
|
|
48
|
+
* reader — the object it is handed is one `published[]` element instead of a
|
|
49
|
+
* whole response — and the event is still one event per promoted item.
|
|
50
|
+
*
|
|
51
|
+
* The name keeps its `Save` prefix because it is public API of this package and
|
|
52
|
+
* renaming it would break consumers for no behavioural gain; {@link door} is
|
|
53
|
+
* what says which write produced the event.
|
|
54
54
|
*/
|
|
55
55
|
interface MetadataSaveAdvisoryEvent {
|
|
56
56
|
/** Metadata type saved (e.g. `'flow'`). */
|
|
@@ -65,15 +65,65 @@ interface MetadataSaveAdvisoryEvent {
|
|
|
65
65
|
* consumer reading the event can tell which door it came through.
|
|
66
66
|
*/
|
|
67
67
|
mode: 'draft' | 'publish';
|
|
68
|
+
/**
|
|
69
|
+
* Which write door produced this event (#5026).
|
|
70
|
+
*
|
|
71
|
+
* - `'save'` — `PUT /meta/:type/:name` ({@link MetadataClient.save}, and the
|
|
72
|
+
* SDK's `meta.saveItem` behind `ObjectStackAdapter`).
|
|
73
|
+
* - `'publish'` — the promotion doors: `POST /meta/:type/:name/publish`
|
|
74
|
+
* ({@link MetadataClient.publish} and {@link MetadataClient.publishDraft}),
|
|
75
|
+
* and the batch `POST /packages/:id/publish-drafts`
|
|
76
|
+
* ({@link MetadataClient.publishPackageDrafts}), which emits ONE event per
|
|
77
|
+
* `published[]` element carrying that element's own `type` / `name`.
|
|
78
|
+
*
|
|
79
|
+
* ## Why the batch route is not a third value (`ce986aafc`)
|
|
80
|
+
*
|
|
81
|
+
* The batch door promotes drafts to active — the author pressed Publish, and
|
|
82
|
+
* every item this event names really was published. What `door` decides is
|
|
83
|
+
* the frame's VERB, and "Published" is the true one for all three routes; a
|
|
84
|
+
* third value would have to render the same word. The per-item identity the
|
|
85
|
+
* author needs to act on is already carried by {@link type} / {@link name},
|
|
86
|
+
* one event per item, so nothing about the batch is lost by sharing the
|
|
87
|
+
* value. ⛔ It is NOT a claim that "one call = one event": the batch emits as
|
|
88
|
+
* many events as it has advised items, which is why each one names its own.
|
|
89
|
+
*
|
|
90
|
+
* Distinct from {@link mode}, which cannot answer this: a direct active save
|
|
91
|
+
* and a draft promotion both report `mode: 'publish'` because both land the
|
|
92
|
+
* body in the active overlay. The renderer needs the DOOR, because the author
|
|
93
|
+
* pressed either Save or Publish and a toast that says "Saved" after a
|
|
94
|
+
* Publish tells them their change is still a draft — the opposite of what
|
|
95
|
+
* happened, and in this product Save and Publish are two different buttons
|
|
96
|
+
* with two different meanings.
|
|
97
|
+
*
|
|
98
|
+
* REQUIRED rather than optional-with-a-default so a future third door cannot
|
|
99
|
+
* be wired without stating which one it is: an omitted discriminator would
|
|
100
|
+
* silently render the save wording.
|
|
101
|
+
*/
|
|
102
|
+
door: 'save' | 'publish';
|
|
68
103
|
/** The findings. Never empty — the event is not emitted otherwise. */
|
|
69
104
|
advisories: RuntimeAuthoringIssue[];
|
|
70
105
|
}
|
|
71
106
|
/** Event listener type for save-advisory events. */
|
|
72
107
|
type MetadataSaveAdvisoryListener = (event: MetadataSaveAdvisoryEvent) => void;
|
|
73
108
|
/**
|
|
74
|
-
* Read the `advisories` array off a
|
|
109
|
+
* Read the `advisories` array off a metadata write response, defensively.
|
|
110
|
+
*
|
|
111
|
+
* Serves EVERY write door unchanged (#5026, `ce986aafc`):
|
|
112
|
+
* `SaveMetaItemResponseSchema` and `PublishMetaItemResponseSchema` declare the
|
|
113
|
+
* key at the same top level, and `PublishPackageDraftsResponseSchema` declares
|
|
114
|
+
* it on each `published[]` element — under the same name, with the same element
|
|
115
|
+
* schema. So there is one reader, not a per-door copy that could drift; the
|
|
116
|
+
* batch caller hands it one element, which is the object that carries the key
|
|
117
|
+
* there.
|
|
75
118
|
*
|
|
76
|
-
*
|
|
119
|
+
* ⛔ It reads the `advisories` key of the object it is GIVEN and never
|
|
120
|
+
* traverses. Choosing the object — a response body at the single-item doors,
|
|
121
|
+
* one `published[]` element at the batch door — is the caller's job precisely
|
|
122
|
+
* because the place is declared per route: a reader that went hunting for
|
|
123
|
+
* findings wherever they might be would be inventing a contract the server
|
|
124
|
+
* never stated.
|
|
125
|
+
*
|
|
126
|
+
* The server omits the key entirely on a clean write, so `undefined` is the
|
|
77
127
|
* common case and means "nothing to say". Anything that is not an array of
|
|
78
128
|
* objects carrying the six required keys is dropped rather than rendered: a
|
|
79
129
|
* half-shaped finding would print blanks at the author, and this channel must
|
|
@@ -105,9 +155,13 @@ interface MetadataClientConfig {
|
|
|
105
155
|
previewDrafts?: boolean;
|
|
106
156
|
/**
|
|
107
157
|
* Called after a {@link MetadataClient.save} whose 2xx response carried a
|
|
108
|
-
* non-empty `advisories` array (objectstack#7435).
|
|
109
|
-
*
|
|
110
|
-
*
|
|
158
|
+
* non-empty `advisories` array (objectstack#7435). Since #5026 the SAME sink
|
|
159
|
+
* also receives the publish door's findings (objectstack#9176), and since
|
|
160
|
+
* `ce986aafc` the batch publish door's too, one event per advised
|
|
161
|
+
* `published[]` element (objectstack#9343) — read `event.door` to tell a
|
|
162
|
+
* save from a promotion, and `event.type` / `event.name` for which item. The
|
|
163
|
+
* write already succeeded; this is how the shell learns there is something to
|
|
164
|
+
* tell the author instead of the findings being discarded client-side.
|
|
111
165
|
*
|
|
112
166
|
* Set on the CONFIG rather than exposed as a `subscribe()` method on purpose:
|
|
113
167
|
* console metadata clients are minted per-component by `useMetadataClient`,
|
|
@@ -136,6 +190,28 @@ interface MetadataDraftHeader {
|
|
|
136
190
|
updatedAt: string | null;
|
|
137
191
|
updatedBy: string | null;
|
|
138
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* What {@link MetadataClient.publishPackageDrafts} resolves with — the "publish
|
|
195
|
+
* whole app" body, DERIVED from `PublishPackageDraftsResponseSchema` rather
|
|
196
|
+
* than re-spelled (`ce986aafc`).
|
|
197
|
+
*
|
|
198
|
+
* Derived, and then widened in exactly two ways, each for a measured reason:
|
|
199
|
+
*
|
|
200
|
+
* - `Partial<…>` because this client talks to runtimes of several vintages and
|
|
201
|
+
* the spec's six required keys are what TODAY's producer sets. Declaring them
|
|
202
|
+
* required would be this client asserting a server version it cannot check —
|
|
203
|
+
* and an older runtime that omits one would be a type lie, not a compile
|
|
204
|
+
* error. Every consumer here already reads these keys defensively.
|
|
205
|
+
* - The index signature because the REST door adds keys on the way out
|
|
206
|
+
* (ADR-0045 visibility receipts, the `metadata:reloaded` announce receipt),
|
|
207
|
+
* and because a body this client hands back must stay readable by a caller
|
|
208
|
+
* that knows about a key this package has never heard of.
|
|
209
|
+
*
|
|
210
|
+
* ⛔ What it is NOT is a second definition of the batch response: the key names
|
|
211
|
+
* and element shapes come from the spec symbol, so a key the spec adds, renames
|
|
212
|
+
* or retypes arrives here with no edit.
|
|
213
|
+
*/
|
|
214
|
+
type MetadataPublishPackageDraftsResult = Partial<PublishPackageDraftsResponse> & Record<string, unknown>;
|
|
139
215
|
/**
|
|
140
216
|
* Options for {@link MetadataClient.save} — a WRITE OVER HTTP to
|
|
141
217
|
* `/api/v1/meta/:type/:name`.
|
|
@@ -146,9 +222,9 @@ interface MetadataDraftHeader {
|
|
|
146
222
|
* metadata item to a FILE — `format: json|yaml|ts`, `path`, `indent`,
|
|
147
223
|
* `prettify`, `sortKeys`, `backup`, `atomic`, `loader`. Not one of those keys
|
|
148
224
|
* exists here, and not one of these exists there: this is the REST client's
|
|
149
|
-
* request envelope — optimistic concurrency (`ifMatch` → `If-Match`),
|
|
150
|
-
*
|
|
151
|
-
*
|
|
225
|
+
* request envelope — optimistic concurrency (`ifMatch` → `If-Match`), the
|
|
226
|
+
* destructive-change override, the ADR-0033 draft/publish mode, and the
|
|
227
|
+
* owning package. Same words, different layer.
|
|
152
228
|
*/
|
|
153
229
|
interface MetadataClientSaveOptions {
|
|
154
230
|
/**
|
|
@@ -157,8 +233,6 @@ interface MetadataClientSaveOptions {
|
|
|
157
233
|
* edits get a 409 instead of overwriting each other.
|
|
158
234
|
*/
|
|
159
235
|
ifMatch?: string;
|
|
160
|
-
/** Optional actor id, sent as `X-Actor` for history attribution. */
|
|
161
|
-
actor?: string;
|
|
162
236
|
/**
|
|
163
237
|
* Bypass destructive-change protection (Phase 3a). The server returns
|
|
164
238
|
* `409 destructive_change` with `issues[]` if a write would drop or
|
|
@@ -203,14 +277,78 @@ interface MetadataDeleteOptions extends MetadataClientSaveOptions {
|
|
|
203
277
|
*/
|
|
204
278
|
state?: 'active' | 'draft';
|
|
205
279
|
}
|
|
206
|
-
/**
|
|
280
|
+
/**
|
|
281
|
+
* Which layer an overlay was saved at — `'org'` for a tenant overlay, `'env'`
|
|
282
|
+
* for an environment-level one, `null` exactly when `overlay` is null.
|
|
283
|
+
*
|
|
284
|
+
* DERIVED from `@objectstack/spec`, not restated: the vocabulary lives once, in
|
|
285
|
+
* `GetMetaItemLayeredResponseSchema`'s `overlayScope`
|
|
286
|
+
* (`z.enum(['org', 'env']).nullable()`), and this alias indexes the published
|
|
287
|
+
* response type so a scope the spec adds arrives here with no edit. Restating
|
|
288
|
+
* the union locally is the fork `scripts/check-spec-symbol-derivation.mjs`
|
|
289
|
+
* exists to reject, and the mirror-image mistake — a consumer re-spelling a
|
|
290
|
+
* producer's enum — is what this field carried until objectui#4982.
|
|
291
|
+
*
|
|
292
|
+
* What it carried: `string | null`, under a comment naming the vocabulary as
|
|
293
|
+
* `organization | environment | package`. Not one of those three spellings is a
|
|
294
|
+
* value the producer emits (`metadata-protocol`'s two assignment sites write
|
|
295
|
+
* `'org'` / `'env'`); the schema rejects all three by name, and `package` is
|
|
296
|
+
* not a scope this field has ever had. Because the declared type was `string`
|
|
297
|
+
* the compiler had no opinion, so the wrong comment was the only description of
|
|
298
|
+
* the vocabulary — a planted premise rather than stale prose, and the reason
|
|
299
|
+
* the Studio's layer badge shipped the raw value straight to screen.
|
|
300
|
+
*/
|
|
301
|
+
type MetadataOverlayScope = GetMetaItemLayeredResponse['overlayScope'];
|
|
302
|
+
/**
|
|
303
|
+
* ADR-0010 §3.6 — the four-state metadata protection lock
|
|
304
|
+
* (`none` / `no-overlay` / `no-delete` / `full`), read from
|
|
305
|
+
* `GetMetaItemLayeredResponseSchema`'s own `z.enum` rather than spelled out
|
|
306
|
+
* again here (objectui#5024).
|
|
307
|
+
*
|
|
308
|
+
* It used to be written out twice in this file, 42 lines apart: once as
|
|
309
|
+
* {@link MetadataLayered.lock} (optional) and once as
|
|
310
|
+
* {@link MetadataAuditEntry.lockState} (nullable), identical in every other
|
|
311
|
+
* respect and compared by no gate. A fifth state added to one would have left
|
|
312
|
+
* the other compiling — the same "declared N times, diffed by nothing" failure
|
|
313
|
+
* objectui#4972 and objectui#4984 record for other vocabularies.
|
|
314
|
+
*
|
|
315
|
+
* Deriving beats a local alias the two merely share. The producer of these
|
|
316
|
+
* values is the framework, and `packages/spec` already declares the vocabulary,
|
|
317
|
+
* so the copies were restating a schema that existed rather than filling a gap.
|
|
318
|
+
* The card that reported this recorded the opposite — "`@objectstack/spec` 也没
|
|
319
|
+
* 有对应的 `z.enum` 可派生" — and so did the audit panel's neighbouring comment;
|
|
320
|
+
* both predate the enum, which ships in `@objectstack/spec` 17.1.0. This is the
|
|
321
|
+
* same treatment {@link MetadataOverlayScope} above already gets, and it closes
|
|
322
|
+
* the cross-repo half of the drift, not just the in-repo half.
|
|
323
|
+
*
|
|
324
|
+
* ⚠️ This types what this repo may WRITE. It does NOT constrain what a server
|
|
325
|
+
* may SEND, and it never will — {@link MetadataClient.layered} forwards the
|
|
326
|
+
* wire value rather than dropping it, so every reader must still handle a value
|
|
327
|
+
* outside these four. What changed in objectui#5676 is that the value is no
|
|
328
|
+
* longer forwarded *silently*: the boundary validates it against the producer's
|
|
329
|
+
* own schema and names the field in {@link MetadataLayered._unrecognized} when
|
|
330
|
+
* it does not match. The lock banner in `ResourceEditPage` is the worked
|
|
331
|
+
* example of a reader that labels such a value instead of rendering it blank.
|
|
332
|
+
*/
|
|
333
|
+
type MetadataLockState = GetMetaItemLayeredResponse['lock'];
|
|
334
|
+
/**
|
|
335
|
+
* Layered view of a metadata item — the body of
|
|
336
|
+
* `GET /meta/:type/:name/layers` (`GetMetaItemLayeredResponseSchema`).
|
|
337
|
+
*
|
|
338
|
+
* A subset by design: the response also carries `type` / `name`, which the
|
|
339
|
+
* caller already knows because it passed them in, so
|
|
340
|
+
* {@link MetadataClient.layered} does not hand them back.
|
|
341
|
+
*/
|
|
207
342
|
interface MetadataLayered<T = unknown> {
|
|
208
343
|
/** Code-level (artifact) item; null if the item only exists as an overlay. */
|
|
209
344
|
code: T | null;
|
|
210
345
|
/** Org/environment overlay (just the saved delta or full overlay row). */
|
|
211
346
|
overlay: T | null;
|
|
212
|
-
/**
|
|
213
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Which layer {@link MetadataLayered.overlay} came from, or null when there
|
|
349
|
+
* is no overlay. Spec-derived — see {@link MetadataOverlayScope}.
|
|
350
|
+
*/
|
|
351
|
+
overlayScope: MetadataOverlayScope;
|
|
214
352
|
/** Merged effective view — what the runtime actually sees. */
|
|
215
353
|
effective: T | null;
|
|
216
354
|
/**
|
|
@@ -222,7 +360,7 @@ interface MetadataLayered<T = unknown> {
|
|
|
222
360
|
*/
|
|
223
361
|
_diagnostics?: MetadataDiagnostics;
|
|
224
362
|
/** 4-state lock: `none` / `no-overlay` / `no-delete` / `full`. */
|
|
225
|
-
lock?:
|
|
363
|
+
lock?: MetadataLockState;
|
|
226
364
|
/** Human-readable reason for the lock (tooltip text). */
|
|
227
365
|
lockReason?: string;
|
|
228
366
|
/** Which layer set the lock: artifact / package / overlay / env-forced. */
|
|
@@ -241,6 +379,21 @@ interface MetadataLayered<T = unknown> {
|
|
|
241
379
|
deletable?: boolean;
|
|
242
380
|
/** True when "Reset to package default" applies (has overlay + artifact). */
|
|
243
381
|
resettable?: boolean;
|
|
382
|
+
/**
|
|
383
|
+
* Protection-envelope keys whose wire value did NOT match `packages/spec`'s
|
|
384
|
+
* schema for them (objectui#5676). Computed HERE, at the boundary — the
|
|
385
|
+
* server never sends this key, and {@link MetadataClient.layered} builds the
|
|
386
|
+
* returned object field by field, so a server that tried could not smuggle
|
|
387
|
+
* one in.
|
|
388
|
+
*
|
|
389
|
+
* Absent when everything the server sent parsed, so its presence is the
|
|
390
|
+
* signal. The offending values are still forwarded on the fields themselves:
|
|
391
|
+
* this is "pass through and label", the treatment objectui#5672 chose for
|
|
392
|
+
* `lock` alone, applied to the whole ADR-0010 envelope. Dropping them would
|
|
393
|
+
* be the reject semantics that card refused — turning a wrong render into no
|
|
394
|
+
* render is a behaviour change for every consumer of this client.
|
|
395
|
+
*/
|
|
396
|
+
_unrecognized?: readonly string[];
|
|
244
397
|
}
|
|
245
398
|
/**
|
|
246
399
|
* One row in the metadata protection-audit trail
|
|
@@ -252,7 +405,10 @@ interface MetadataAuditEntry {
|
|
|
252
405
|
id: unknown;
|
|
253
406
|
/** ISO timestamp when the attempt happened. */
|
|
254
407
|
occurredAt: string;
|
|
255
|
-
/**
|
|
408
|
+
/**
|
|
409
|
+
* Who attempted the operation — the identity the request was authorized
|
|
410
|
+
* as, or `'system'` for internal machine writes (objectstack#7941).
|
|
411
|
+
*/
|
|
256
412
|
actor: string;
|
|
257
413
|
/** Code path that recorded the row (e.g. `protocol.saveMetaItem`). */
|
|
258
414
|
source: string | null;
|
|
@@ -263,7 +419,7 @@ interface MetadataAuditEntry {
|
|
|
263
419
|
/** Machine-readable reason code (`item_locked`, `ok`, …). */
|
|
264
420
|
code: string;
|
|
265
421
|
/** Effective lock at the moment of the attempt. */
|
|
266
|
-
lockState:
|
|
422
|
+
lockState: MetadataLockState | null;
|
|
267
423
|
/** True when admin forced the write through despite the lock. */
|
|
268
424
|
lockOverridden: boolean;
|
|
269
425
|
/** Request-id for trace correlation (if propagated). */
|
|
@@ -382,7 +538,7 @@ declare class MetadataClient {
|
|
|
382
538
|
private readonly headers;
|
|
383
539
|
/** ADR-0037: when true, reads render the draft-overlaid world. */
|
|
384
540
|
readonly previewDrafts: boolean;
|
|
385
|
-
/** #4133 — sink for
|
|
541
|
+
/** #4133 / #5026 — sink for every write door's advisory findings; see the config field. */
|
|
386
542
|
private readonly onSaveAdvisory;
|
|
387
543
|
constructor(config: MetadataClientConfig);
|
|
388
544
|
/** Update the client's environment scope at runtime. */
|
|
@@ -456,12 +612,42 @@ declare class MetadataClient {
|
|
|
456
612
|
* {@link get} is deliberate and long-standing (the draft envelope's identity
|
|
457
613
|
* and protection carriers are part of what a draft reader inspects), so it
|
|
458
614
|
* is preserved by reading the transport directly instead of going through
|
|
459
|
-
* `get()`'s unwrap.
|
|
460
|
-
*
|
|
615
|
+
* `get()`'s unwrap.
|
|
616
|
+
*
|
|
617
|
+
* ⛔ Do NOT hand `.item` to a gate or a write yourself: the body this serves
|
|
618
|
+
* is DECORATED (`_draft`, then `_diagnostics` from `decorateMetadataItem`),
|
|
619
|
+
* and the spec says a served body is not a valid input to the schema that
|
|
620
|
+
* produced it until those come off. {@link extractDraftBody} is the one
|
|
621
|
+
* reader for this method's answer — it takes the presence verdict and then
|
|
622
|
+
* the strip, in that order. That function used to be copy-pasted into four
|
|
623
|
+
* views and only one copy knew the rule (objectui#8181).
|
|
624
|
+
*
|
|
625
|
+
* Its tolerant siblings exist for the OTHER client method: `unwrapDraftBody`
|
|
626
|
+
* (app-shell) and `unwrapViewDraft` (this package) read a draft that arrived
|
|
627
|
+
* through {@link get}, which already unwrapped the envelope. They split on
|
|
628
|
+
* which method feeds them, not on what they mean — all three strip.
|
|
461
629
|
*/
|
|
462
630
|
getDraft<T = unknown>(type: string, name: string, options?: {
|
|
463
631
|
packageId?: string;
|
|
464
632
|
}): Promise<T | null>;
|
|
633
|
+
/**
|
|
634
|
+
* Report the runtime authoring gate's advisory findings for a write the
|
|
635
|
+
* server has ALREADY committed (#4133 for the save door, #5026 for the
|
|
636
|
+
* publish door).
|
|
637
|
+
*
|
|
638
|
+
* Everything here is best-effort by construction, and that is the whole
|
|
639
|
+
* contract: the row is committed server-side before this runs, so neither a
|
|
640
|
+
* malformed body, nor a missing key, nor a throwing sink may change what the
|
|
641
|
+
* calling method returns or whether it throws. The server emits `advisories`
|
|
642
|
+
* ONLY when non-empty, so a clean write costs one absent-key check.
|
|
643
|
+
*
|
|
644
|
+
* One helper rather than a copy per door: every door's response declares the
|
|
645
|
+
* key identically, so a second inline copy could only ever drift. The batch
|
|
646
|
+
* door (`ce986aafc`) calls it once per `published[]` element, handing it
|
|
647
|
+
* the element — the object that carries the key there — and the same
|
|
648
|
+
* best-effort contract covers that loop unchanged.
|
|
649
|
+
*/
|
|
650
|
+
private emitAdvisories;
|
|
465
651
|
/**
|
|
466
652
|
* Save (PUT) a metadata item. The framework accepts both the bare
|
|
467
653
|
* item payload and the `{ item: ... }` / `{ metadata: ... }`
|
|
@@ -480,6 +666,61 @@ declare class MetadataClient {
|
|
|
480
666
|
* the rows and reports under `seedApplied` — a data problem never fails the
|
|
481
667
|
* publish, so callers should check `seedApplied?.success` and warn the user
|
|
482
668
|
* rather than assume the data went live.
|
|
669
|
+
*
|
|
670
|
+
* Same door as {@link publish} (`POST /meta/:type/:name/publish`), so it
|
|
671
|
+
* reports the gate's advisories the same way (#5026).
|
|
672
|
+
*
|
|
673
|
+
* The BATCH door (`POST /packages/:id/publish-drafts`) is a different route
|
|
674
|
+
* with a method of its own — {@link publishPackageDrafts} — and it reports
|
|
675
|
+
* too. It did not when this paragraph was first written: it discarded
|
|
676
|
+
* per-draft advisories server-side, which was objectstack#9343, and that card
|
|
677
|
+
* has since landed with a ruling that each `published[]` element carries
|
|
678
|
+
* them (`ce986aafc` is the client half).
|
|
679
|
+
*
|
|
680
|
+
* What survives that change is the READING RULE, which is about this method
|
|
681
|
+
* rather than about the other route: it reads the top level of the
|
|
682
|
+
* single-item body and nothing else. A batch-shaped body arriving HERE still
|
|
683
|
+
* reports nothing, because `PublishMetaItemResponse` declares no
|
|
684
|
+
* `published[]` and a client that went looking for findings wherever they
|
|
685
|
+
* might be would be inventing a contract instead of reading one.
|
|
686
|
+
*
|
|
687
|
+
* ## Why there is no `{ success, data }` unwrapping here (`7b433197d`)
|
|
688
|
+
*
|
|
689
|
+
* This method used to unwrap a dispatcher-shaped envelope before returning,
|
|
690
|
+
* while {@link publish} — the same route, a couple of hundred lines down —
|
|
691
|
+
* did not. Two spellings of one wire contract, and nothing said which was
|
|
692
|
+
* right. The tolerance is gone because the route was MEASURED, at the
|
|
693
|
+
* producer rather than inferred from the response schema:
|
|
694
|
+
*
|
|
695
|
+
* - The only mount of `POST /api/v1/meta/:type/:name/publish` is the REST
|
|
696
|
+
* server's own route-manager registration, which answers
|
|
697
|
+
* `res.json(await protocol.publishMetaItem(...))` — the protocol's object
|
|
698
|
+
* verbatim, with no envelope branch. The project-scoped mirror
|
|
699
|
+
* (`/api/v1/environments/:environmentId/...`) re-mounts that same handler.
|
|
700
|
+
* - The `{ success, data }` envelope has exactly one producer, the runtime
|
|
701
|
+
* `HttpDispatcher`, and it does not serve this route: its `/meta` domain
|
|
702
|
+
* has no publish branch, the dispatcher's own route ledger has no row for
|
|
703
|
+
* it, and a three-segment `/meta` path that is not `/published` terminates
|
|
704
|
+
* in a located `routeNotFound`. So no composition — REST server, the
|
|
705
|
+
* dispatcher-only Hono adapter, or both — puts an envelope on this body.
|
|
706
|
+
* - `packages/spec` states the same split in the two docstrings side by
|
|
707
|
+
* side: `PublishMetaItemResponseSchema` describes "the FULL body" this
|
|
708
|
+
* route returns and records that the REST route hands the producer's
|
|
709
|
+
* object to `res.json()` verbatim, while its batch sibling
|
|
710
|
+
* `PublishPackageDraftsResponseSchema` describes a body answered
|
|
711
|
+
* explicitly "inside the dispatcher's `{ success, data }` envelope".
|
|
712
|
+
*
|
|
713
|
+
* ⛔ So an enveloped body on THIS route is not a shape the platform emits,
|
|
714
|
+
* and unwrapping one is not a kindness: it is Commandment #0.1's lenient
|
|
715
|
+
* fallback, a second de-facto contract for a route that declares one. It
|
|
716
|
+
* also fails in the direction that hides the problem — a body that arrived
|
|
717
|
+
* enveloped is one this door did not actually serve, and unwrapping it
|
|
718
|
+
* would present that as a successful promotion.
|
|
719
|
+
*
|
|
720
|
+
* ⚠️ Sibling tolerances in this file are NOT this defect and must stay:
|
|
721
|
+
* {@link listDrafts} reads `data?.data?.drafts` because `GET /meta/_drafts`
|
|
722
|
+
* IS a dispatcher route, and `publishHealthFromResponse` unwraps because the
|
|
723
|
+
* batch door really is enveloped. The rule is per-route, not per-file.
|
|
483
724
|
*/
|
|
484
725
|
publishDraft(type: string, name: string): Promise<{
|
|
485
726
|
success?: boolean;
|
|
@@ -492,9 +733,82 @@ declare class MetadataClient {
|
|
|
492
733
|
};
|
|
493
734
|
} & Record<string, unknown>>;
|
|
494
735
|
/**
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
736
|
+
* Publish EVERY pending draft bound to one package — Studio's "publish whole
|
|
737
|
+
* app" door, `POST /api/v1/packages/:id/publish-drafts` (ADR-0033; the route
|
|
738
|
+
* that orders structure-before-seeds server-side and runs the ADR-0038 L3
|
|
739
|
+
* runtime probes).
|
|
740
|
+
*
|
|
741
|
+
* ## Why it is expressed here at all (`ce986aafc`)
|
|
742
|
+
*
|
|
743
|
+
* It was not, and that was the defect. Two app-shell call sites fired this
|
|
744
|
+
* route with a bare `fetch` / a page-private `apiJson`, outside the seam that
|
|
745
|
+
* covers every other metadata write — so when objectstack#9343 landed and the
|
|
746
|
+
* server began sending per-draft advisories, the author publishing a whole
|
|
747
|
+
* app was told nothing, while the SAME button's client-side capability lint
|
|
748
|
+
* still raised a toast. A door that cannot report is not a door the gate can
|
|
749
|
+
* reach, however loudly the server speaks.
|
|
750
|
+
*
|
|
751
|
+
* ## What it reports, and what it refuses to invent
|
|
752
|
+
*
|
|
753
|
+
* `PublishPackageDraftsResponseSchema` declares `advisories` on EACH
|
|
754
|
+
* `published[]` element — objectstack#9343's ruled shape, the same element
|
|
755
|
+
* type and the same omitted-when-empty discipline as the single-item door,
|
|
756
|
+
* and explicitly not a parallel top-level map. So this method emits one
|
|
757
|
+
* {@link MetadataSaveAdvisoryEvent} per advised element, each naming that
|
|
758
|
+
* element's own `type` / `name`, through the same sink, event and renderer
|
|
759
|
+
* the save and single-item publish doors use.
|
|
760
|
+
*
|
|
761
|
+
* ⛔ It renders only what the server sent where the schema says it sits:
|
|
762
|
+
*
|
|
763
|
+
* - `advisories` is read off the element, by the one shared reader, which
|
|
764
|
+
* drops anything that is not a complete finding.
|
|
765
|
+
* - An element that does not carry the `type` and `name` the schema states as
|
|
766
|
+
* REQUIRED is skipped rather than reported under invented identity — the
|
|
767
|
+
* event names the item the author has to go fix, and an event that cannot
|
|
768
|
+
* name it truthfully is worse than silence.
|
|
769
|
+
* - Nothing is derived, counted or summarised from the batch: no advisory
|
|
770
|
+
* exists here that the server did not put in the body.
|
|
771
|
+
*
|
|
772
|
+
* ## The failure shapes stay the callers'
|
|
773
|
+
*
|
|
774
|
+
* Non-2xx throws {@link MetadataError}, like every other method. A 2xx is
|
|
775
|
+
* RETURNED unexamined — `success: false` is not a failure on this route
|
|
776
|
+
* (`outcome: 'nothing_to_publish'` answers it too, and the spec's own text
|
|
777
|
+
* says to read `outcome`, not the boolean), and the two callers have their
|
|
778
|
+
* own, different rules for the refusal and rolled-back cases. Deciding that
|
|
779
|
+
* here would change behaviour this card is not about.
|
|
780
|
+
*/
|
|
781
|
+
publishPackageDrafts(packageId: string): Promise<MetadataPublishPackageDraftsResult>;
|
|
782
|
+
/**
|
|
783
|
+
* Get the 3-state layered view of a metadata item: `code` (the packaged
|
|
784
|
+
* artifact baseline), `overlay` (the tenant customisation row alone) and
|
|
785
|
+
* `effective` (the merged value the runtime sees).
|
|
786
|
+
*
|
|
787
|
+
* Reads **`GET /meta/:type/:name/layers`** — the path the framework declares
|
|
788
|
+
* for this projection, with a response schema of its own
|
|
789
|
+
* (`GetMetaItemLayeredResponseSchema`, objectstack#5882 ruling B). It used to
|
|
790
|
+
* be reached by hanging a `layers` flag on the ordinary item read, which made
|
|
791
|
+
* one route answer two unrelated representations while `packages/spec`
|
|
792
|
+
* declared only one of them. That spelling still answers this same body
|
|
793
|
+
* inside its deprecation window (the response carries RFC 9745
|
|
794
|
+
* `Deprecation: true` and an RFC 8288 `Link: rel="successor-version"` back to
|
|
795
|
+
* this path) and is scheduled for removal upstream, so nothing here may
|
|
796
|
+
* depend on it — the repo-wide ratchet is
|
|
797
|
+
* `scripts/__tests__/layered-read-declared-path-4016.test.ts`.
|
|
798
|
+
*
|
|
799
|
+
* The request is built here rather than delegated to `@objectstack/client`
|
|
800
|
+
* because the SDK expresses no layered read in EITHER spelling: the
|
|
801
|
+
* framework's REST route ledger records this route as `server-only`,
|
|
802
|
+
* "consumed by objectui over plain HTTP", and whether the SDK should express
|
|
803
|
+
* it is an open upstream product call.
|
|
804
|
+
*
|
|
805
|
+
* One behaviour delta rides along with the path, and it is the server's
|
|
806
|
+
* choice rather than ours: the retired flag FELL THROUGH to the plain item
|
|
807
|
+
* read on a backend whose protocol implementation had no layered support,
|
|
808
|
+
* answering the `{ type, name, item }` envelope. A dedicated path refuses to
|
|
809
|
+
* answer a different resource under this one's declared shape, so it returns
|
|
810
|
+
* 501 `NOT_IMPLEMENTED` instead — which surfaces here as a thrown error
|
|
811
|
+
* rather than a view with `code` and `overlay` silently blank.
|
|
498
812
|
*/
|
|
499
813
|
layered<T = unknown>(type: string, name: string, options?: {
|
|
500
814
|
packageId?: string;
|
|
@@ -528,10 +842,16 @@ declare class MetadataClient {
|
|
|
528
842
|
* `{ success, version, seq, message }`. Throws a `404 no_draft` if
|
|
529
843
|
* nothing is pending and `409 metadata_conflict` if the published
|
|
530
844
|
* overlay moved while the draft was sitting.
|
|
845
|
+
*
|
|
846
|
+
* Reports the runtime authoring gate's advisory findings through
|
|
847
|
+
* {@link MetadataClientConfig.onSaveAdvisory} when the response carries them
|
|
848
|
+
* (#5026 / objectstack#9176), with `door: 'publish'`. This is the door the
|
|
849
|
+
* Studio designer takes on every edit, so it is the one whose findings an
|
|
850
|
+
* author actually sees.
|
|
531
851
|
*/
|
|
532
852
|
publish<T = unknown>(type: string, name: string, options?: {
|
|
533
|
-
actor?: string;
|
|
534
853
|
message?: string;
|
|
854
|
+
packageId?: string;
|
|
535
855
|
}): Promise<T>;
|
|
536
856
|
/**
|
|
537
857
|
* Restore an item to a previous version (POST `/meta/:type/:name/rollback`).
|
|
@@ -542,7 +862,6 @@ declare class MetadataClient {
|
|
|
542
862
|
* when the target version is a delete tombstone.
|
|
543
863
|
*/
|
|
544
864
|
rollback<T = unknown>(type: string, name: string, toVersion: number, options?: {
|
|
545
|
-
actor?: string;
|
|
546
865
|
message?: string;
|
|
547
866
|
}): Promise<T>;
|
|
548
867
|
/**
|
|
@@ -747,66 +1066,6 @@ interface MetadataCacheStats {
|
|
|
747
1066
|
hitRate: number;
|
|
748
1067
|
}
|
|
749
1068
|
|
|
750
|
-
/**
|
|
751
|
-
* ObjectUI
|
|
752
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
753
|
-
*
|
|
754
|
-
* This source code is licensed under the MIT license found in the
|
|
755
|
-
* LICENSE file in the root directory of this source tree.
|
|
756
|
-
*/
|
|
757
|
-
/**
|
|
758
|
-
* Contracts module integration for @objectstack/spec v3.0.0
|
|
759
|
-
* Provides plugin contract validation and marketplace publishing utilities.
|
|
760
|
-
*/
|
|
761
|
-
interface PluginContract {
|
|
762
|
-
/** Plugin name */
|
|
763
|
-
name: string;
|
|
764
|
-
/** Plugin version */
|
|
765
|
-
version: string;
|
|
766
|
-
/** Required peer dependencies */
|
|
767
|
-
peerDependencies?: Record<string, string>;
|
|
768
|
-
/** Exported component types */
|
|
769
|
-
exports: PluginExport[];
|
|
770
|
-
/** Required permissions */
|
|
771
|
-
permissions?: string[];
|
|
772
|
-
/** API surface contract */
|
|
773
|
-
api?: PluginAPIContract;
|
|
774
|
-
}
|
|
775
|
-
interface PluginExport {
|
|
776
|
-
/** Export name */
|
|
777
|
-
name: string;
|
|
778
|
-
/** Export type */
|
|
779
|
-
type: 'component' | 'hook' | 'utility' | 'provider';
|
|
780
|
-
/** Description */
|
|
781
|
-
description?: string;
|
|
782
|
-
}
|
|
783
|
-
interface PluginAPIContract {
|
|
784
|
-
/** Consumed data sources */
|
|
785
|
-
dataSources?: string[];
|
|
786
|
-
/** Required object schemas */
|
|
787
|
-
requiredSchemas?: string[];
|
|
788
|
-
/** Event subscriptions */
|
|
789
|
-
events?: string[];
|
|
790
|
-
}
|
|
791
|
-
interface ContractValidationResult {
|
|
792
|
-
valid: boolean;
|
|
793
|
-
errors: ContractValidationError[];
|
|
794
|
-
warnings: string[];
|
|
795
|
-
}
|
|
796
|
-
interface ContractValidationError {
|
|
797
|
-
field: string;
|
|
798
|
-
message: string;
|
|
799
|
-
code: string;
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Validate a plugin contract against the ObjectStack spec.
|
|
803
|
-
*/
|
|
804
|
-
declare function validatePluginContract(contract: PluginContract): ContractValidationResult;
|
|
805
|
-
/**
|
|
806
|
-
* Generate a plugin contract manifest for marketplace publishing.
|
|
807
|
-
*/
|
|
808
|
-
declare function generateContractManifest(contract: PluginContract): Record<string, unknown>;
|
|
809
|
-
|
|
810
1069
|
/**
|
|
811
1070
|
* ObjectStack-backed `UserDataAdapter` factory.
|
|
812
1071
|
*
|
|
@@ -887,342 +1146,225 @@ interface UserDataAdapter<T> {
|
|
|
887
1146
|
declare function createObjectStackUserStateAdapter<T = unknown>(options: ObjectStackUserStateAdapterOptions): UserDataAdapter<T>;
|
|
888
1147
|
|
|
889
1148
|
/**
|
|
890
|
-
*
|
|
891
|
-
*
|
|
1149
|
+
* Take the body out of a served draft envelope (`{ type, name, item }`) and
|
|
1150
|
+
* remove the framework's own READ DECORATIONS from it — or answer `null` when
|
|
1151
|
+
* there is nothing pending.
|
|
892
1152
|
*
|
|
893
|
-
*
|
|
894
|
-
* LICENSE file in the root directory of this source tree.
|
|
895
|
-
*/
|
|
896
|
-
/**
|
|
897
|
-
* Integration module for @objectstack/spec v3.0.0
|
|
898
|
-
* Provides third-party service connectors for Slack, email, webhooks.
|
|
899
|
-
*/
|
|
900
|
-
type IntegrationProvider = 'slack' | 'email' | 'webhook' | 'teams' | 'discord';
|
|
901
|
-
interface IntegrationConfig {
|
|
902
|
-
/** Integration provider type */
|
|
903
|
-
provider: IntegrationProvider;
|
|
904
|
-
/** Whether this integration is enabled */
|
|
905
|
-
enabled: boolean;
|
|
906
|
-
/** Provider-specific configuration */
|
|
907
|
-
config: Record<string, unknown>;
|
|
908
|
-
/** Event triggers */
|
|
909
|
-
triggers?: IntegrationTrigger[];
|
|
910
|
-
}
|
|
911
|
-
interface IntegrationTrigger {
|
|
912
|
-
/** Event name (e.g. 'record.created', 'record.updated') */
|
|
913
|
-
event: string;
|
|
914
|
-
/** Filter condition */
|
|
915
|
-
filter?: string;
|
|
916
|
-
/** Template for the message/payload */
|
|
917
|
-
template?: string;
|
|
918
|
-
}
|
|
919
|
-
interface SlackIntegrationConfig extends IntegrationConfig {
|
|
920
|
-
provider: 'slack';
|
|
921
|
-
config: {
|
|
922
|
-
webhookUrl: string;
|
|
923
|
-
channel?: string;
|
|
924
|
-
username?: string;
|
|
925
|
-
iconEmoji?: string;
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
interface EmailIntegrationConfig extends IntegrationConfig {
|
|
929
|
-
provider: 'email';
|
|
930
|
-
config: {
|
|
931
|
-
smtpHost: string;
|
|
932
|
-
smtpPort: number;
|
|
933
|
-
secure: boolean;
|
|
934
|
-
from: string;
|
|
935
|
-
to: string[];
|
|
936
|
-
subject?: string;
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
interface WebhookIntegrationConfig extends IntegrationConfig {
|
|
940
|
-
provider: 'webhook';
|
|
941
|
-
config: {
|
|
942
|
-
url: string;
|
|
943
|
-
method: 'GET' | 'POST' | 'PUT' | 'PATCH';
|
|
944
|
-
headers?: Record<string, string>;
|
|
945
|
-
retryCount?: number;
|
|
946
|
-
retryDelay?: number;
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* Integration service manager.
|
|
951
|
-
* Manages third-party service connections and event dispatching.
|
|
952
|
-
*/
|
|
953
|
-
declare class IntegrationManager {
|
|
954
|
-
private integrations;
|
|
955
|
-
/**
|
|
956
|
-
* Register a new integration.
|
|
957
|
-
*/
|
|
958
|
-
register(id: string, config: IntegrationConfig): void;
|
|
959
|
-
/**
|
|
960
|
-
* Remove an integration.
|
|
961
|
-
*/
|
|
962
|
-
unregister(id: string): void;
|
|
963
|
-
/**
|
|
964
|
-
* Get all registered integrations.
|
|
965
|
-
*/
|
|
966
|
-
getAll(): Map<string, IntegrationConfig>;
|
|
967
|
-
/**
|
|
968
|
-
* Get integrations that match a specific event.
|
|
969
|
-
*/
|
|
970
|
-
getForEvent(event: string): IntegrationConfig[];
|
|
971
|
-
/**
|
|
972
|
-
* Dispatch an event to all matching integrations.
|
|
973
|
-
* Returns results for each integration.
|
|
974
|
-
*/
|
|
975
|
-
dispatch(event: string, payload: Record<string, unknown>): Promise<Array<{
|
|
976
|
-
id: string;
|
|
977
|
-
success: boolean;
|
|
978
|
-
error?: string;
|
|
979
|
-
}>>;
|
|
980
|
-
/**
|
|
981
|
-
* Send payload to a specific integration.
|
|
982
|
-
*/
|
|
983
|
-
private send;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* ObjectUI
|
|
988
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
1153
|
+
* ## Why this function exists at all (objectui#8181)
|
|
989
1154
|
*
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
|
|
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.
|
|
1155
|
+
* It was implemented three separate times — once in `ResourceEditPage`, once in
|
|
1156
|
+
* `StudioDesignSurface`, once in `PackageOwdOverviewPanel` — plus a fourth
|
|
1157
|
+
* hand-rolled copy in `ObjectHooksPanel` under a different name. objectui#7603
|
|
1158
|
+
* taught exactly ONE of those copies to strip. That is the defect this file
|
|
1159
|
+
* closes, and it is a different defect from "N consumers forgot": four
|
|
1160
|
+
* byte-identical copies of a rule mean the next copy is free to omit it again,
|
|
1161
|
+
* so the cure is one function rather than four strips.
|
|
1001
1162
|
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
retentionDays?: number;
|
|
1041
|
-
/** External log destination */
|
|
1042
|
-
destination?: 'console' | 'server' | 'both';
|
|
1043
|
-
}
|
|
1044
|
-
type AuditEventType = 'auth.login' | 'auth.logout' | 'auth.failed' | 'data.create' | 'data.read' | 'data.update' | 'data.delete' | 'admin.config' | 'admin.user' | 'admin.role';
|
|
1045
|
-
interface DataMaskingConfig {
|
|
1046
|
-
/** Fields to mask */
|
|
1047
|
-
rules: DataMaskingRule[];
|
|
1048
|
-
/** Default masking character */
|
|
1049
|
-
maskChar?: string;
|
|
1050
|
-
}
|
|
1051
|
-
interface DataMaskingRule {
|
|
1052
|
-
/** Field name or pattern */
|
|
1053
|
-
field: string;
|
|
1054
|
-
/** Masking strategy */
|
|
1055
|
-
strategy: 'full' | 'partial' | 'hash' | 'redact';
|
|
1056
|
-
/** Number of visible characters (for partial masking) */
|
|
1057
|
-
visibleChars?: number;
|
|
1058
|
-
/** Roles that can see unmasked data */
|
|
1059
|
-
exemptRoles?: string[];
|
|
1060
|
-
}
|
|
1061
|
-
interface AuditLogEntry {
|
|
1062
|
-
/** Event timestamp */
|
|
1063
|
-
timestamp: string;
|
|
1064
|
-
/** Event type */
|
|
1065
|
-
event: AuditEventType;
|
|
1066
|
-
/** User who triggered the event */
|
|
1067
|
-
userId: string;
|
|
1068
|
-
/** Target resource */
|
|
1069
|
-
resource?: string;
|
|
1070
|
-
/** Record ID */
|
|
1071
|
-
recordId?: string;
|
|
1072
|
-
/** Additional details */
|
|
1073
|
-
details?: Record<string, unknown>;
|
|
1074
|
-
/** IP address */
|
|
1075
|
-
ipAddress?: string;
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Security policy manager.
|
|
1079
|
-
* Handles CSP generation, audit logging, and data masking.
|
|
1163
|
+
* ## Why it lives HERE and not in `app-shell`
|
|
1164
|
+
*
|
|
1165
|
+
* Next to the {@link MetadataClient.getDraft} that produces the envelope this
|
|
1166
|
+
* decodes. `getDraft` deliberately hands back the wire envelope rather than the
|
|
1167
|
+
* body (objectui#4271), so the unwrap is part of that method's contract, not a
|
|
1168
|
+
* detail of any one view. `app-shell` already depends on this package, and the
|
|
1169
|
+
* one non-app-shell consumer (`updateView`, in this package) is a caller too.
|
|
1170
|
+
*
|
|
1171
|
+
* ## The verdict runs BEFORE the strip, never after
|
|
1172
|
+
*
|
|
1173
|
+
* What counts as a pending draft is `getDraft`'s answer. A draft whose only
|
|
1174
|
+
* keys are decorations is still a served draft, so the emptiness verdict is
|
|
1175
|
+
* taken on the body as it arrived; the strip may return an EMPTY object, but it
|
|
1176
|
+
* may never turn a served draft into "no draft". Pinned in
|
|
1177
|
+
* `draft-envelope.test.ts`.
|
|
1178
|
+
*
|
|
1179
|
+
* ## Which keys, and which keys deliberately survive
|
|
1180
|
+
*
|
|
1181
|
+
* The list is the SPEC'S (`METADATA_READ_DECORATIONS`), reached through its own
|
|
1182
|
+
* exported helper — the same one `MetadataService.saveFields` uses on the write
|
|
1183
|
+
* side. Never a second hand-maintained `['_diagnostics', '_draft']` in this
|
|
1184
|
+
* repo: a local copy goes stale the next time the framework adds a decoration,
|
|
1185
|
+
* and a decoration this code does not know to remove is precisely the defect.
|
|
1186
|
+
*
|
|
1187
|
+
* The ADR-0010 protection envelope (`_lock`, `_provenance`, `_packageId`,
|
|
1188
|
+
* `_packageVersion`, ...) shares the underscore spelling and is deliberately
|
|
1189
|
+
* NOT on that list: those keys are declared by the closed schemas so provenance
|
|
1190
|
+
* survives a re-parse. This strip leaves them alone, and doing otherwise would
|
|
1191
|
+
* be the same "drop whatever looks internal" pass AGENTS.md #0.1 bans.
|
|
1192
|
+
*
|
|
1193
|
+
* ## Not a lenient fallback
|
|
1194
|
+
*
|
|
1195
|
+
* It removes exactly the two keys the framework ADDS AT READ TIME and never
|
|
1196
|
+
* stores. A genuinely unrecognized key still fails loudly at whichever gate
|
|
1197
|
+
* sees it next.
|
|
1198
|
+
*
|
|
1199
|
+
* @param resp the value `MetadataClient.getDraft()` resolved to.
|
|
1200
|
+
* @returns the decoration-free draft body, or `null` for "nothing pending".
|
|
1080
1201
|
*/
|
|
1081
|
-
declare
|
|
1082
|
-
private policy;
|
|
1083
|
-
private auditLog;
|
|
1084
|
-
constructor(policy?: SecurityManagerPolicy);
|
|
1085
|
-
/**
|
|
1086
|
-
* Generate a CSP header string from the configuration.
|
|
1087
|
-
*/
|
|
1088
|
-
generateCSPHeader(): string;
|
|
1089
|
-
/**
|
|
1090
|
-
* Record an audit log entry.
|
|
1091
|
-
*/
|
|
1092
|
-
recordAudit(entry: Omit<AuditLogEntry, 'timestamp'>): void;
|
|
1093
|
-
/**
|
|
1094
|
-
* Get audit log entries.
|
|
1095
|
-
*/
|
|
1096
|
-
getAuditLog(filter?: {
|
|
1097
|
-
event?: AuditEventType;
|
|
1098
|
-
userId?: string;
|
|
1099
|
-
since?: string;
|
|
1100
|
-
}): AuditLogEntry[];
|
|
1101
|
-
/**
|
|
1102
|
-
* Apply data masking to a record.
|
|
1103
|
-
*/
|
|
1104
|
-
maskRecord(record: Record<string, unknown>, userRoles?: string[]): Record<string, unknown>;
|
|
1105
|
-
/**
|
|
1106
|
-
* Update the security policy.
|
|
1107
|
-
*/
|
|
1108
|
-
updatePolicy(policy: Partial<SecurityManagerPolicy>): void;
|
|
1109
|
-
/**
|
|
1110
|
-
* Get current security policy.
|
|
1111
|
-
*/
|
|
1112
|
-
getPolicy(): SecurityManagerPolicy;
|
|
1113
|
-
}
|
|
1202
|
+
declare function extractDraftBody(resp: unknown): Record<string, unknown> | null;
|
|
1114
1203
|
|
|
1115
1204
|
/**
|
|
1116
|
-
*
|
|
1117
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
1205
|
+
* One issue → a single field-anchored line: `• fields.amount.type — Required`.
|
|
1118
1206
|
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Studio module integration for @objectstack/spec v3.0.0
|
|
1124
|
-
* Provides visual designer schema improvements: canvas, property editors, theme builder.
|
|
1125
|
-
*/
|
|
1126
|
-
interface StudioCanvasConfig {
|
|
1127
|
-
/** Canvas width */
|
|
1128
|
-
width: number;
|
|
1129
|
-
/** Canvas height */
|
|
1130
|
-
height: number;
|
|
1131
|
-
/** Background type */
|
|
1132
|
-
background: 'grid' | 'dots' | 'lines' | 'none';
|
|
1133
|
-
/** Grid size in pixels */
|
|
1134
|
-
gridSize: number;
|
|
1135
|
-
/** Snap to grid */
|
|
1136
|
-
snapToGrid: boolean;
|
|
1137
|
-
/** Zoom range */
|
|
1138
|
-
zoom: {
|
|
1139
|
-
min: number;
|
|
1140
|
-
max: number;
|
|
1141
|
-
step: number;
|
|
1142
|
-
current: number;
|
|
1143
|
-
};
|
|
1144
|
-
/** Pan offset */
|
|
1145
|
-
panOffset: {
|
|
1146
|
-
x: number;
|
|
1147
|
-
y: number;
|
|
1148
|
-
};
|
|
1149
|
-
/** Show minimap */
|
|
1150
|
-
showMinimap: boolean;
|
|
1151
|
-
/** Minimap position */
|
|
1152
|
-
minimapPosition: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
1153
|
-
}
|
|
1154
|
-
interface StudioPropertyEditor {
|
|
1155
|
-
/** Property name */
|
|
1156
|
-
name: string;
|
|
1157
|
-
/** Display label */
|
|
1158
|
-
label: string;
|
|
1159
|
-
/** Editor type */
|
|
1160
|
-
type: 'text' | 'number' | 'boolean' | 'select' | 'color' | 'code' | 'expression';
|
|
1161
|
-
/** Default value */
|
|
1162
|
-
defaultValue?: unknown;
|
|
1163
|
-
/** Options for select type */
|
|
1164
|
-
options?: Array<{
|
|
1165
|
-
label: string;
|
|
1166
|
-
value: string;
|
|
1167
|
-
}>;
|
|
1168
|
-
/** Group/category */
|
|
1169
|
-
group?: string;
|
|
1170
|
-
/** Description */
|
|
1171
|
-
description?: string;
|
|
1172
|
-
/** Whether the property supports live preview */
|
|
1173
|
-
livePreview?: boolean;
|
|
1174
|
-
}
|
|
1175
|
-
interface StudioThemeBuilderConfig {
|
|
1176
|
-
/** Available color palettes */
|
|
1177
|
-
palettes: StudioColorPalette[];
|
|
1178
|
-
/** Typography presets */
|
|
1179
|
-
typography: StudioTypographyPreset[];
|
|
1180
|
-
/** Spacing scale */
|
|
1181
|
-
spacing: number[];
|
|
1182
|
-
/** Border radius options */
|
|
1183
|
-
borderRadius: number[];
|
|
1184
|
-
/** Shadow presets */
|
|
1185
|
-
shadows: StudioShadowPreset[];
|
|
1186
|
-
}
|
|
1187
|
-
interface StudioColorPalette {
|
|
1188
|
-
name: string;
|
|
1189
|
-
colors: Record<string, string>;
|
|
1190
|
-
}
|
|
1191
|
-
interface StudioTypographyPreset {
|
|
1192
|
-
name: string;
|
|
1193
|
-
fontFamily: string;
|
|
1194
|
-
fontSize: Record<string, string>;
|
|
1195
|
-
fontWeight: Record<string, number>;
|
|
1196
|
-
lineHeight: Record<string, string>;
|
|
1197
|
-
}
|
|
1198
|
-
interface StudioShadowPreset {
|
|
1199
|
-
name: string;
|
|
1200
|
-
value: string;
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Default canvas configuration for designers.
|
|
1207
|
+
* Exported so a caller that formats a LIST of failures — `app-shell`'s publish
|
|
1208
|
+
* formatter, whose `failed[]` entries carry their own `issues` — writes each
|
|
1209
|
+
* issue in this same grammar instead of keeping a second copy of it.
|
|
1204
1210
|
*/
|
|
1205
|
-
declare function
|
|
1211
|
+
declare function formatMetadataIssue(i: MetadataValidationIssue): string;
|
|
1206
1212
|
/**
|
|
1207
|
-
*
|
|
1213
|
+
* Format a caught save/publish error for a banner/toast. When the error carries
|
|
1214
|
+
* spec-validation issues, list them (one field per line) so the user sees the
|
|
1215
|
+
* offending fields and what to do about them; otherwise fall back to the plain
|
|
1216
|
+
* message.
|
|
1208
1217
|
*/
|
|
1209
|
-
declare function
|
|
1210
|
-
|
|
1211
|
-
y: number;
|
|
1212
|
-
};
|
|
1218
|
+
declare function formatMetadataError(e: unknown): string;
|
|
1219
|
+
|
|
1213
1220
|
/**
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1221
|
+
* The object-metadata write invariant, placed at the DOOR rather than at the
|
|
1222
|
+
* writers (objectui#8676).
|
|
1223
|
+
*
|
|
1224
|
+
* ## Why this module exists — the enumeration, not the count
|
|
1225
|
+
*
|
|
1226
|
+
* objectui#7714 ruled one client behaviour: *a half-filled relationship stays
|
|
1227
|
+
* client-side and the PUT body never carries a `lookup` without a non-empty
|
|
1228
|
+
* `reference`*. Its PR implemented that ruling by naming the writers it knew
|
|
1229
|
+
* about — two of them — and putting the assertion inside each one's array-to-map
|
|
1230
|
+
* conversion (`toFieldsMap`). The very failure the ruling was written against
|
|
1231
|
+
* was then reproduced on a THIRD writer neither conversion covers, in that
|
|
1232
|
+
* card's own required dogfood (fixed in `9073cf018`). objectui#8676 swept and
|
|
1233
|
+
* found nine more.
|
|
1234
|
+
*
|
|
1235
|
+
* The durable half of that card is a sentence about method, not a number:
|
|
1236
|
+
*
|
|
1237
|
+
* > a ruling that enumerates writers is only as good as the enumeration.
|
|
1238
|
+
*
|
|
1239
|
+
* A hand list of writers is stale the next time somebody adds one, and nothing
|
|
1240
|
+
* says so. So this invariant is not attached to writers at all. It is attached
|
|
1241
|
+
* to the DOORS — the places that put bytes on the wire — because the doors are a
|
|
1242
|
+
* CLOSED set that this repository owns, while the writers are an OPEN set it
|
|
1243
|
+
* does not. Three in-repo transports can PUT `/meta/:type/:name`, and
|
|
1244
|
+
* `scripts/check-object-metadata-write-doors.mjs` derives that set on every run
|
|
1245
|
+
* rather than restating it:
|
|
1246
|
+
*
|
|
1247
|
+
* 1. `MetadataClient.save` `packages/data-objectstack/src/metadata-client.ts`
|
|
1248
|
+
* 2. `importObjectDraft` `packages/app-shell/src/views/metadata-admin/external/api.ts`
|
|
1249
|
+
* 3. the `@objectstack/client` SDK's `meta.saveItem`, reached in this repo
|
|
1250
|
+
* through `MetadataService`'s one seam
|
|
1251
|
+
*
|
|
1252
|
+
* Every writer in the repo reaches the server through one of those three. Guard
|
|
1253
|
+
* them and the writer count stops mattering; add a fourth transport and the gate
|
|
1254
|
+
* turns red naming it. That is the whole design: ⛔ do not re-open this by
|
|
1255
|
+
* adding a writer list anywhere.
|
|
1256
|
+
*
|
|
1257
|
+
* ## What it asserts, and the two things it deliberately does NOT
|
|
1258
|
+
*
|
|
1259
|
+
* It asserts two field-level invariants, and nothing else:
|
|
1260
|
+
*
|
|
1261
|
+
* - objectui#7714's: a relationship (`lookup` / `master_detail`) carries a
|
|
1262
|
+
* usable `reference`;
|
|
1263
|
+
* - objectui#11253's: a choice (`select` / `radio`) carries an option source,
|
|
1264
|
+
* a non-empty `options` list or a shared `picklist`. This is the objectui
|
|
1265
|
+
* branch of the maintainer's ruling A on objectstack#20827, whose door
|
|
1266
|
+
* refuses "a `select` / `radio` with neither `options` nor `picklist`".
|
|
1267
|
+
*
|
|
1268
|
+
* ⛔ **NOT a client-side revalidation of the document.** Parsing the whole body
|
|
1269
|
+
* through `ObjectSchema` before the PUT would refuse plugin-registered keys the
|
|
1270
|
+
* SERVER accepts, and it would promote a client PREDICTION into a block — which
|
|
1271
|
+
* is exactly what objectui#4306 / objectui#6980 ruled against for the designer's
|
|
1272
|
+
* live Zod pass, on the stated ground that a schema issue on a draft the server
|
|
1273
|
+
* ACCEPTS would dead-bolt Save with no on-screen editor able to clear it.
|
|
1274
|
+
*
|
|
1275
|
+
* The two invariants stand in different relations to the server, and the
|
|
1276
|
+
* dead-bolt argument is made separately for each:
|
|
1277
|
+
*
|
|
1278
|
+
* - The relationship refusal forecloses nothing the server would have taken:
|
|
1279
|
+
* the same body is refused one layer down with a 422 on
|
|
1280
|
+
* `fields.NAME.reference`, so the guard only moves an identical refusal
|
|
1281
|
+
* earlier, where it can name the field while it is still on screen.
|
|
1282
|
+
* - The choice refusal is deliberately AHEAD of the server. The installed
|
|
1283
|
+
* `@objectstack/spec` still accepts a choice with no option source, and the
|
|
1284
|
+
* ruling closes the client order first on purpose — its execution parameter
|
|
1285
|
+
* (2) reads "if Studio saves a `select` before its options exist, the
|
|
1286
|
+
* objectui side changes its order first (a card in that lane) and the door
|
|
1287
|
+
* closes after". So this refusal does hold a body the server would store
|
|
1288
|
+
* today. It is not a dead-bolt, for the reason the ruling gives and the
|
|
1289
|
+
* reason the editors give: the ruling makes the same body a server refusal
|
|
1290
|
+
* once the door closes (a stored row is named and every later save of that
|
|
1291
|
+
* object is refused until it gains options), and the message names the
|
|
1292
|
+
* field on every surface that holds one. The Studio data page and the
|
|
1293
|
+
* metadata-admin object editor carry an options editor in that field's
|
|
1294
|
+
* inspector, so the hold clears on screen, including for an object that
|
|
1295
|
+
* already stores such a field (both pinned by their
|
|
1296
|
+
* `*.choiceWithoutOptions-11253` suites). plugin-designer's drawer has no
|
|
1297
|
+
* options editor, so it stops offering the choice types; a stored `select`
|
|
1298
|
+
* keeps its type there and can be retyped or removed, and a stored `radio`
|
|
1299
|
+
* is among the types that page carries through unedited and sends to
|
|
1300
|
+
* metadata-admin, as it already does for a target-less `master_detail`.
|
|
1301
|
+
* `object-metadata-write-guard.derivation.test.ts` re-measures "still
|
|
1302
|
+
* accepted by the installed server" on every run; when the door ships and
|
|
1303
|
+
* the pin moves, that reading turns red, and this paragraph collapses into
|
|
1304
|
+
* the relationship one above.
|
|
1305
|
+
*
|
|
1306
|
+
* ⛔ **NOT strip-and-report-saved.** Dropping the offending field and reporting
|
|
1307
|
+
* success would trade a visible refusal for an invisible deletion —
|
|
1308
|
+
* objectstack#4001's shape, ruled out for this family in objectui#7714 and again
|
|
1309
|
+
* in `9073cf018`. It throws; the caller surfaces the message.
|
|
1310
|
+
*
|
|
1311
|
+
* ## The type lists are pinned to the contract, not to memory
|
|
1312
|
+
*
|
|
1313
|
+
* {@link RELATIONSHIP_TYPES_REQUIRING_REFERENCE} and
|
|
1314
|
+
* {@link CHOICE_TYPES_REQUIRING_OPTIONS} are the two lists this module keeps,
|
|
1315
|
+
* and they are the same class of hazard the module exists to close, so neither
|
|
1316
|
+
* gets to be a remembered list either.
|
|
1317
|
+
* `object-metadata-write-guard.derivation.test.ts` DERIVES both from the
|
|
1318
|
+
* installed `@objectstack/spec`:
|
|
1319
|
+
*
|
|
1320
|
+
* - the relationship set from every member of `FieldType`, parsed as
|
|
1321
|
+
* `{ type, label }` through `FieldSchema`, keeping those refused at path
|
|
1322
|
+
* `reference`;
|
|
1323
|
+
* - the choice set from the same members run through `checkFieldCompleteness`
|
|
1324
|
+
* (ADR-0078's author-time rule, which the ruling's door adopts), keeping
|
|
1325
|
+
* those it reports as `field/choice-without-options` at `error` severity.
|
|
1326
|
+
* `FieldSchema` cannot answer this one yet, because the door has not shipped.
|
|
1327
|
+
*
|
|
1328
|
+
* A spec release that changes either set turns that pin red instead of leaving
|
|
1329
|
+
* a guard that quietly stopped covering the contract.
|
|
1330
|
+
*
|
|
1331
|
+
* The derivation is a TEST and not a runtime probe on purpose: parsing 50
|
|
1332
|
+
* field-type documents on the way to every save is a cost paid on the hot path
|
|
1333
|
+
* to re-learn something that changes at most once per spec release, and a probe
|
|
1334
|
+
* that throws at runtime has to choose between blocking writes and failing open.
|
|
1335
|
+
* A pin has neither problem — it fails at CI time, loudly, with nothing at stake.
|
|
1336
|
+
*/
|
|
1337
|
+
/**
|
|
1338
|
+
* Field types whose `reference` — the target object a relationship links to —
|
|
1339
|
+
* `@objectstack/spec` requires to be present and non-empty.
|
|
1340
|
+
*
|
|
1341
|
+
* Derived from the installed artifact by the pin named above, never recalled.
|
|
1342
|
+
*/
|
|
1343
|
+
declare const RELATIONSHIP_TYPES_REQUIRING_REFERENCE: readonly string[];
|
|
1344
|
+
/**
|
|
1345
|
+
* Field types that offer nothing to choose without an option source — a
|
|
1346
|
+
* non-empty `options` list or a shared `picklist` (objectui#11253, the objectui
|
|
1347
|
+
* branch of the maintainer's ruling A on objectstack#20827).
|
|
1348
|
+
*
|
|
1349
|
+
* Derived from the installed artifact by the pin named above, never recalled.
|
|
1350
|
+
*/
|
|
1351
|
+
declare const CHOICE_TYPES_REQUIRING_OPTIONS: readonly string[];
|
|
1352
|
+
/** The metadata type whose documents carry the `fields` map this guard reads. */
|
|
1353
|
+
declare const OBJECT_METADATA_TYPE = "object";
|
|
1354
|
+
/**
|
|
1355
|
+
* Refuse an object-metadata write whose body carries a relationship field with
|
|
1356
|
+
* no usable target, or a choice field with no option source.
|
|
1357
|
+
*
|
|
1358
|
+
* A no-op for every other metadata type and for any body with no readable
|
|
1359
|
+
* `fields` member — this door serves `view`, `app`, `flow`, `permission`,
|
|
1360
|
+
* `hook` and `dashboard` writes too, and has no opinion about them.
|
|
1361
|
+
*
|
|
1362
|
+
* @param type the metadata type segment of the write (`'object'`, `'view'`, …)
|
|
1363
|
+
* @param item the body about to be serialised onto the wire
|
|
1364
|
+
* @param writer a short label for the door, so the message names where it fired
|
|
1365
|
+
* @throws Error naming the offending field, before any request is issued
|
|
1366
|
+
*/
|
|
1367
|
+
declare function assertObjectMetadataWritable(type: unknown, item: unknown, writer: string): void;
|
|
1226
1368
|
|
|
1227
1369
|
/**
|
|
1228
1370
|
* Map human-readable filter operator names produced by SDUI view configs
|
|
@@ -1260,6 +1402,156 @@ declare class MalformedFilterError extends Error {
|
|
|
1260
1402
|
}
|
|
1261
1403
|
/** Detect the malformed-filter refusal, whether raised here or by the server. */
|
|
1262
1404
|
declare function isMalformedFilterError(error: unknown): boolean;
|
|
1405
|
+
/**
|
|
1406
|
+
* A `where` that reached `aggregate()`'s SPEC-SHAPE branch as an array the spec
|
|
1407
|
+
* cannot read as a filter — an unlowered `ViewFilterRule[]` above all.
|
|
1408
|
+
*
|
|
1409
|
+
* WHY THIS IS A REFUSAL AND NOT A LOWERING (objectui#6825, maintainer ruling
|
|
1410
|
+
* 2026-08-30, option A). The two branches of `aggregate()` take a filter by
|
|
1411
|
+
* different names: the analytics branch takes `filter` and lowers it through
|
|
1412
|
+
* `translateFilterArray` (objectui#6302), the spec-shape branch takes `where`
|
|
1413
|
+
* and posts it to `POST /data/:object/query` verbatim. Lowering here too was
|
|
1414
|
+
* option B and was REFUSED: it is the tolerant-consumer direction AGENTS.md
|
|
1415
|
+
* #0.1 exists to stop, and it would bless as supported a params shape
|
|
1416
|
+
* `AggregateParams` does not even declare. Retiring the branch was option D and
|
|
1417
|
+
* was refused as the riskier move — this repo has no producer that can reach it
|
|
1418
|
+
* (`ObjectChart`'s gate requires a non-array `aggregate.groupBy`, and all three
|
|
1419
|
+
* in-tree constructors pass a string), but `ObjectChart`'s props are `any` and
|
|
1420
|
+
* an out-of-repo host may already send this shape. So the branch stays, and it
|
|
1421
|
+
* says no out loud.
|
|
1422
|
+
*
|
|
1423
|
+
* What that buys is the FAILURE MODE, not a failure count. The receiving engine
|
|
1424
|
+
* already refuses these arrays ("is not a filter" — objectstack's
|
|
1425
|
+
* `engine-filter-array-lowering.test.ts`), so nothing that reaches this point
|
|
1426
|
+
* could have produced a correct number; the pre-existing behaviour was to find
|
|
1427
|
+
* that out on the wire, or worse, to have the predicate quietly dropped while
|
|
1428
|
+
* the chart rendered confident, wrong figures with no signal to their author.
|
|
1429
|
+
* This moves the same refusal to the producer, where the author can act on it.
|
|
1430
|
+
*
|
|
1431
|
+
* Carries the `INVALID_FILTER` / 400 pair its sibling {@link MalformedFilterError}
|
|
1432
|
+
* carries, so `isMalformedFilterError()` recognises it and a failed widget renders
|
|
1433
|
+
* "this filter is malformed" rather than "check your connection" (objectui#3066).
|
|
1434
|
+
*/
|
|
1435
|
+
declare class UnloweredAggregateWhereError extends Error {
|
|
1436
|
+
readonly code = "INVALID_FILTER";
|
|
1437
|
+
readonly httpStatus = 400;
|
|
1438
|
+
/** The value as received, so a caller can log what its producer actually built. */
|
|
1439
|
+
readonly where: unknown;
|
|
1440
|
+
/** The object `aggregate()` was called for. */
|
|
1441
|
+
readonly resource: string;
|
|
1442
|
+
constructor(where: unknown, resource: string);
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* The keys `aggregate()`'s ANALYTICS branch reads and its SPEC-SHAPE branch does
|
|
1446
|
+
* not: `filter`, `field`, `function`.
|
|
1447
|
+
*
|
|
1448
|
+
* Written out once, and read by both the refusal below and its message, so the
|
|
1449
|
+
* set a caller is told about cannot drift from the set that is checked.
|
|
1450
|
+
*
|
|
1451
|
+
* ⛔ Deliberately NOT "every key the spec-shape branch does not read". A refusal
|
|
1452
|
+
* scoped that widely would reject `orderBy`, a future spec key, or any harmless
|
|
1453
|
+
* extra a host happens to carry, and it would satisfy a naive "the spec-shape
|
|
1454
|
+
* branch refuses `filter`" test while breaking traffic nobody complained about.
|
|
1455
|
+
* These three are named one by one because they are the OTHER branch's own
|
|
1456
|
+
* parameters: their presence says the params were built for the analytics wire
|
|
1457
|
+
* and arrived on the spec-shape one, which is a producer defect with a name.
|
|
1458
|
+
*/
|
|
1459
|
+
declare const ANALYTICS_ONLY_AGGREGATE_KEYS: readonly ["filter", "field", "function"];
|
|
1460
|
+
type AnalyticsOnlyAggregateKey = (typeof ANALYTICS_ONLY_AGGREGATE_KEYS)[number];
|
|
1461
|
+
/**
|
|
1462
|
+
* Analytics-branch params — `filter`, `field`, `function` — that reached
|
|
1463
|
+
* `aggregate()`'s SPEC-SHAPE branch, which reads none of them.
|
|
1464
|
+
*
|
|
1465
|
+
* WHY A REFUSAL AND NOT A DROP (`503cd8b89`). This applies the maintainer
|
|
1466
|
+
* ruling of 2026-08-30 on objectui#6825 — option A, REFUSE at the producer — to
|
|
1467
|
+
* the rest of the same branch. That ruling's reason was that a shape the spec's
|
|
1468
|
+
* own gate would reject is off-contract at the PRODUCER, so the adapter says no
|
|
1469
|
+
* instead of degrading leniently; the reason lands harder here, because a
|
|
1470
|
+
* silent drop is precisely the disposition it refused for `where`. Leaving one
|
|
1471
|
+
* key on this branch refusing while three others vanish without a word would be
|
|
1472
|
+
* harder to explain than the old behaviour.
|
|
1473
|
+
*
|
|
1474
|
+
* ⭐ AND THE DROP HERE IS WORSE THAN THE `where` HALF. `filter` is not the only
|
|
1475
|
+
* casualty: `field` and `function` are the analytics branch's whole measure, and
|
|
1476
|
+
* the spec-shape branch copies a measure only out of `aggregations`. So the
|
|
1477
|
+
* legacy shape `{ field, function, groupBy, filter }` whose `groupBy` happens to
|
|
1478
|
+
* be an ARRAY produced a `queryAst` carrying a `groupBy` and NO `aggregations`
|
|
1479
|
+
* at all — a grouping with no measure — with the author's predicate gone too.
|
|
1480
|
+
* The chart still rendered, the numbers were wrong, and there was nothing to
|
|
1481
|
+
* look at.
|
|
1482
|
+
*
|
|
1483
|
+
* HOW A LEGACY CALLER GETS HERE, measured in this repo on 2026-09-08.
|
|
1484
|
+
* `Array.isArray(params.groupBy)` is one of `looksLikeSpecShape`'s three
|
|
1485
|
+
* disjuncts, and `ObjectChart`'s `runAggregate` gates only on the STRUCTURED
|
|
1486
|
+
* node shape (`gb && typeof gb === 'object' && !Array.isArray(gb)`), so an
|
|
1487
|
+
* ARRAY `aggregate.groupBy` falls through to its legacy call — `{ field,
|
|
1488
|
+
* function, groupBy, filter }` — and that call lands here.
|
|
1489
|
+
* `ObjectMetricWidget.computeOne` forwards `aggregate.groupBy` the same way.
|
|
1490
|
+
* Both take the value from authored widget metadata across an `any` seam
|
|
1491
|
+
* (`isObjectProvider`'s `aggregate?: any`, `ds: any`), so the type system
|
|
1492
|
+
* cannot refuse it and this runtime refusal is what an author gets instead.
|
|
1493
|
+
*
|
|
1494
|
+
* ⛔ WHAT THIS DELIBERATELY DOES NOT DO. It does not ROUTE the legacy shape back
|
|
1495
|
+
* to the analytics branch: that is the tolerant-consumer direction AGENTS.md
|
|
1496
|
+
* §0.1 exists to stop, it is the option #6825 refused for `where`, and it could
|
|
1497
|
+
* not work anyway — the analytics branch posts `dimensions: [params.groupBy]`,
|
|
1498
|
+
* so an array `groupBy` would go out nested. It does not widen `AggregateParams`
|
|
1499
|
+
* (`@object-ui/types`), which is a separate contract question. And it does not
|
|
1500
|
+
* refuse a key that is present but nullish: `undefined`/`null` carries nothing
|
|
1501
|
+
* for the branch to drop, and both `ObjectChart` and `ObjectMetricWidget` build
|
|
1502
|
+
* their params by spreading possibly-absent authored values.
|
|
1503
|
+
*
|
|
1504
|
+
* Carries the `INVALID_FILTER` / 400 pair its siblings {@link MalformedFilterError}
|
|
1505
|
+
* and {@link UnloweredAggregateWhereError} carry, so `isMalformedFilterError()`
|
|
1506
|
+
* recognises it and a failed widget renders "this filter is malformed" rather
|
|
1507
|
+
* than "check your connection" (objectui#3066). One branch, one envelope: a
|
|
1508
|
+
* caller can catch `aggregate()`'s producer-side refusals with one predicate.
|
|
1509
|
+
*/
|
|
1510
|
+
declare class AnalyticsKeysOnSpecShapeError extends Error {
|
|
1511
|
+
readonly code = "INVALID_FILTER";
|
|
1512
|
+
readonly httpStatus = 400;
|
|
1513
|
+
/** The offending keys, in the order {@link ANALYTICS_ONLY_AGGREGATE_KEYS} lists them. */
|
|
1514
|
+
readonly keys: readonly AnalyticsOnlyAggregateKey[];
|
|
1515
|
+
/** What each offending key carried, so a producer is identifiable from a log. */
|
|
1516
|
+
readonly received: Record<string, unknown>;
|
|
1517
|
+
/** The object `aggregate()` was called for. */
|
|
1518
|
+
readonly resource: string;
|
|
1519
|
+
constructor(keys: readonly AnalyticsOnlyAggregateKey[], params: any, resource: string);
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* An ARRAY `filter` that reached `aggregate()`'s ANALYTICS branch and that the
|
|
1523
|
+
* protocol's lowering sink cannot turn into a `FilterCondition` — an infix join
|
|
1524
|
+
* (`[condA, 'or', condB]`) above all.
|
|
1525
|
+
*
|
|
1526
|
+
* WHY A REFUSAL AND NOT A DROP (objectui#7752; maintainer ruling of 2026-09-05
|
|
1527
|
+
* on objectstack#15828, which reads that card as a frontend defect: the
|
|
1528
|
+
* protocol is right and the adapter was wrong). `parseFilterAST` answers
|
|
1529
|
+
* `undefined` for an array it cannot read, and `undefined` here would post a
|
|
1530
|
+
* body with NO `where` at all: the aggregate would then be computed over every
|
|
1531
|
+
* row and returned as a confident number, with the author's predicate gone and
|
|
1532
|
+
* nothing on the wire to say it went. That is the same silent over-fetch
|
|
1533
|
+
* {@link MalformedFilterError} exists to stop, one door further along.
|
|
1534
|
+
*
|
|
1535
|
+
* Deliberately NOT {@link UnloweredAggregateWhereError}, whose meaning is not
|
|
1536
|
+
* widened to cover this: that one guards the SPEC-SHAPE branch, whose `where`
|
|
1537
|
+
* is posted verbatim and must therefore have been lowered by its PRODUCER
|
|
1538
|
+
* (objectui#6825, ruling 2026-08-30 option A — refuse, never lower). This
|
|
1539
|
+
* branch's `filter` is authoring input and IS lowered here, at the door the
|
|
1540
|
+
* protocol names; this error covers only the input that sink cannot lower.
|
|
1541
|
+
*
|
|
1542
|
+
* Carries the `INVALID_FILTER` / 400 pair both siblings carry, so
|
|
1543
|
+
* `isMalformedFilterError()` recognises it and a failed widget renders "this
|
|
1544
|
+
* filter is malformed" rather than "check your connection" (objectui#3066).
|
|
1545
|
+
*/
|
|
1546
|
+
declare class UnlowerableAnalyticsFilterError extends Error {
|
|
1547
|
+
readonly code = "INVALID_FILTER";
|
|
1548
|
+
readonly httpStatus = 400;
|
|
1549
|
+
/** The filter as received, so a caller can log what its producer actually built. */
|
|
1550
|
+
readonly filter: unknown;
|
|
1551
|
+
/** The object `aggregate()` was called for. */
|
|
1552
|
+
readonly resource: string;
|
|
1553
|
+
constructor(filter: unknown, resource: string, detail?: string);
|
|
1554
|
+
}
|
|
1263
1555
|
/**
|
|
1264
1556
|
* Serialize a `$orderby` to the server's `sort` shorthand
|
|
1265
1557
|
* (`field,-other_field`), for every shape `QueryParams['$orderby']` declares.
|
|
@@ -1292,7 +1584,7 @@ declare function getSharedDiscovery(baseUrl: string, fetcher: () => Promise<unkn
|
|
|
1292
1584
|
declare function clearSharedDiscoveryCache(): void;
|
|
1293
1585
|
/**
|
|
1294
1586
|
* Read the cross-object atomic-batch capability from a `discovery` document
|
|
1295
|
-
* (
|
|
1587
|
+
* (objectstack-ai/objectstack#3298 / objectui #2693). The server advertises it hierarchically
|
|
1296
1588
|
* under `capabilities.transactionalBatch.enabled`; the published
|
|
1297
1589
|
* `@objectstack/client` also accepts the flat `capabilities.transactionalBatch:
|
|
1298
1590
|
* boolean` form and normalizes the two — mirror that here so the adapter reads
|
|
@@ -1305,7 +1597,7 @@ declare function clearSharedDiscoveryCache(): void;
|
|
|
1305
1597
|
* real error.
|
|
1306
1598
|
* - `false` — the backend explicitly does NOT (route absent, or a runtime that
|
|
1307
1599
|
* can't open a transaction).
|
|
1308
|
-
* - `undefined` — the capability is absent, i.e. the backend predates #3298;
|
|
1600
|
+
* - `undefined` — the capability is absent, i.e. the backend predates objectstack-ai/objectstack#3298;
|
|
1309
1601
|
* the caller must keep the legacy runtime-probe fallback (we can't tell
|
|
1310
1602
|
* whether `/batch` exists without trying it).
|
|
1311
1603
|
*/
|
|
@@ -1358,22 +1650,38 @@ declare const API_ACCESS_DENIED_CODES: readonly ["OBJECT_API_DISABLED", "OBJECT_
|
|
|
1358
1650
|
declare function isApiAccessDeniedError(error: unknown): boolean;
|
|
1359
1651
|
/**
|
|
1360
1652
|
* What the by-name meta app route said about THIS session's access to an app
|
|
1361
|
-
* (objectui#4252 / objectstack#8013).
|
|
1362
|
-
*
|
|
1363
|
-
* - `granted`
|
|
1364
|
-
* - `denied`
|
|
1365
|
-
* The only verdict a caller may render as an
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
1373
|
-
* the
|
|
1374
|
-
*
|
|
1375
|
-
|
|
1376
|
-
|
|
1653
|
+
* (objectui#4252 / objectstack#8013, widened by objectui#9262).
|
|
1654
|
+
*
|
|
1655
|
+
* - `granted` — the route served the app document.
|
|
1656
|
+
* - `denied` — the app EXISTS and the session lacks its
|
|
1657
|
+
* `requiredPermissions`. The only verdict a caller may render as an
|
|
1658
|
+
* authorization refusal.
|
|
1659
|
+
* - `not_found` — the route answered, and what it answered is that it has no
|
|
1660
|
+
* app at this name for this session. WHICH absence it is — never created, a
|
|
1661
|
+
* typo, an unpublished draft, an app gated by an absent optional service —
|
|
1662
|
+
* is deliberately NOT distinguished: the control plane answers the same way
|
|
1663
|
+
* for every app the caller may not see, and the 2026-08-12 ruling keeps it
|
|
1664
|
+
* that way. A caller may say the app cannot be opened; it may never say why.
|
|
1665
|
+
* - `unreachable` — the probe could not obtain an answer at all: the transport
|
|
1666
|
+
* failed, the server erred, the request timed out. Says nothing about the
|
|
1667
|
+
* app.
|
|
1668
|
+
* - `unknown` — nothing was measured, because nothing could be asked: a
|
|
1669
|
+
* host DataSource that does not implement this probe (the console is
|
|
1670
|
+
* protocol-agnostic — AGENTS #1), or no app name to ask about.
|
|
1671
|
+
*
|
|
1672
|
+
* ## Why `not_found` and `unreachable` are separate members (objectui#9262)
|
|
1673
|
+
*
|
|
1674
|
+
* They were one `unknown` before, and the screen above them therefore asserted
|
|
1675
|
+
* a transient publish state over both. The distinction is not new information
|
|
1676
|
+
* to obtain — it is already on the wire, and folding it away was the defect.
|
|
1677
|
+
* The maintainer ruling (2026-09-13) named the two members; this type is the
|
|
1678
|
+
* two-member widening that ruling declared.
|
|
1679
|
+
*
|
|
1680
|
+
* ⚠️ `unknown` no longer means "absent". A consumer that treats `unknown` as a
|
|
1681
|
+
* positive absence is reading a verdict that now means the opposite: that the
|
|
1682
|
+
* question was never put.
|
|
1683
|
+
*/
|
|
1684
|
+
type AppAccessVerdict = 'granted' | 'denied' | 'not_found' | 'unreachable' | 'unknown';
|
|
1377
1685
|
/**
|
|
1378
1686
|
* The ADR-0112 standard catalog code the by-name meta app route answers with
|
|
1379
1687
|
* when an app exists and the session lacks its `requiredPermissions`
|
|
@@ -1397,9 +1705,87 @@ declare const APP_PERMISSION_DENIED_CODE = "PERMISSION_DENIED";
|
|
|
1397
1705
|
* are errors.
|
|
1398
1706
|
*/
|
|
1399
1707
|
declare function isAppPermissionDeniedError(error: unknown): boolean;
|
|
1708
|
+
/**
|
|
1709
|
+
* The ADR-0112 standard catalog code the metadata routes answer with when the
|
|
1710
|
+
* named item is not there for this caller (`sendError(res, 404,
|
|
1711
|
+
* 'RESOURCE_NOT_FOUND', …)` in `packages/rest/src/rest-server.ts`).
|
|
1712
|
+
*
|
|
1713
|
+
* One code covers every absence on purpose: a nonexistent name, an unpublished
|
|
1714
|
+
* app (ADR-0045 §3 keeps it externally unobservable), and an app gated by an
|
|
1715
|
+
* absent optional service (ADR-0057 D10 — nothing was denied to the CALLER) are
|
|
1716
|
+
* one answer by the 2026-08-12 ruling's design, not three that happen to
|
|
1717
|
+
* collide. Consumers may report the absence; they may not report a reason.
|
|
1718
|
+
*/
|
|
1719
|
+
declare const META_ITEM_ABSENT_CODE = "RESOURCE_NOT_FOUND";
|
|
1720
|
+
/**
|
|
1721
|
+
* True when `error` is a metadata route's "not here for you" answer.
|
|
1722
|
+
*
|
|
1723
|
+
* Discriminates on the ADR-0112 `code`, never on the status, for the same
|
|
1724
|
+
* reason {@link isAppPermissionDeniedError} does (objectui#4408): a 404 is a
|
|
1725
|
+
* transport fact many conditions share — a mis-typed route, a proxy, an
|
|
1726
|
+
* appliance in front of the server — while the code is the contract. A 404 that
|
|
1727
|
+
* carries no declared code is NOT an absence: nobody said the item was missing,
|
|
1728
|
+
* so the honest reading is that no answer was obtained.
|
|
1729
|
+
*/
|
|
1730
|
+
declare function isMetaItemAbsentError(error: unknown): boolean;
|
|
1731
|
+
/**
|
|
1732
|
+
* [ADR-0066] The system capability a view-configuration write requires.
|
|
1733
|
+
*
|
|
1734
|
+
* objectstack#7494's ruling (maintainer, 2026-08-12) settled what this store
|
|
1735
|
+
* IS: the `sort` / `hiddenFields` / `columnState` / `rowHeight` persisted by
|
|
1736
|
+
* {@link ObjectStackAdapter.updateViewConfig} are ORG-WIDE view configuration
|
|
1737
|
+
* — shared by every user of the view — not a per-user preference. A per-user
|
|
1738
|
+
* scope is parked (objectstack#7611, v18) and deliberately NOT built here, so
|
|
1739
|
+
* there is no second, narrower store for an ungated write to fall back to:
|
|
1740
|
+
* the only thing this write can do is change the view for everyone.
|
|
1741
|
+
*
|
|
1742
|
+
* That makes a toolbar toggle a metadata-authoring act, so it gates on the
|
|
1743
|
+
* capability this repo ALREADY uses for metadata authoring rather than a newly
|
|
1744
|
+
* minted name. `manage_metadata` is what `HomePage`'s `AUTHORING_CAPABILITY`
|
|
1745
|
+
* gates the builder CTAs on, what the server itself refuses metadata writes
|
|
1746
|
+
* without (`PackageFormDialog` reads that 403), and what
|
|
1747
|
+
* `capability.label.manage_metadata` is already translated as in all ten
|
|
1748
|
+
* locale packs. Decisively: the write gated here goes through
|
|
1749
|
+
* `client.meta.saveItem` — the very same ADR-0005 metadata door — so this is
|
|
1750
|
+
* the same authority the server is already applying, not an analogous one.
|
|
1751
|
+
*/
|
|
1752
|
+
declare const VIEW_CONFIG_CAPABILITY = "manage_metadata";
|
|
1753
|
+
/**
|
|
1754
|
+
* Thrown by {@link ObjectStackAdapter.updateViewConfig} when the session's
|
|
1755
|
+
* REPORTED capability set does not contain {@link VIEW_CONFIG_CAPABILITY}.
|
|
1756
|
+
*
|
|
1757
|
+
* Raised BEFORE `connect()` and before `saveItem`, so a refused call issues no
|
|
1758
|
+
* request at all — the org-wide row is not touched, not even optimistically.
|
|
1759
|
+
*
|
|
1760
|
+
* Deliberately an ERROR and not a silent `return`. The production caller is a
|
|
1761
|
+
* debounced toolbar toggle whose UI has ALREADY moved by the time this runs, so
|
|
1762
|
+
* a quiet no-op would leave the operator looking at a density they did not get,
|
|
1763
|
+
* with nothing to explain it and a reload to discover it. The message names the
|
|
1764
|
+
* SCOPE first and the capability second, in that order, because the scope is
|
|
1765
|
+
* the part the operator cannot otherwise see — and it is written to be shown
|
|
1766
|
+
* as-is, the same contract {@link AnalyticsNotInstalledError} states.
|
|
1767
|
+
*/
|
|
1768
|
+
declare class ViewConfigPermissionDeniedError extends Error {
|
|
1769
|
+
readonly code = "VIEW_CONFIG_PERMISSION_DENIED";
|
|
1770
|
+
/** The capability that was required and not held. */
|
|
1771
|
+
readonly capability = "manage_metadata";
|
|
1772
|
+
/** The object whose view configuration the refused write targeted. */
|
|
1773
|
+
readonly objectName: string;
|
|
1774
|
+
/** The view id the refused write targeted. */
|
|
1775
|
+
readonly viewId: string;
|
|
1776
|
+
constructor(objectName: string, viewId: string);
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* True when `error` is a {@link ViewConfigPermissionDeniedError}.
|
|
1780
|
+
*
|
|
1781
|
+
* Duck-checks `code`/`name` rather than using `instanceof`, matching
|
|
1782
|
+
* {@link isConcurrentUpdateError}: a host that bundles this package twice
|
|
1783
|
+
* (or re-throws across a worker boundary) still gets the right verdict.
|
|
1784
|
+
*/
|
|
1785
|
+
declare function isViewConfigPermissionDeniedError(error: unknown): error is ViewConfigPermissionDeniedError;
|
|
1400
1786
|
/**
|
|
1401
1787
|
* Thrown when the deployment has no analytics capability installed
|
|
1402
|
-
* (framework#3891 / #4019).
|
|
1788
|
+
* (framework#3891 / objectstack-ai/objectstack#4019).
|
|
1403
1789
|
*
|
|
1404
1790
|
* The framework retired its degraded in-kernel analytics fallback — it dropped
|
|
1405
1791
|
* the caller's RLS/tenant scope and ignored the contract filter, so it answered
|
|
@@ -1416,10 +1802,132 @@ declare class AnalyticsNotInstalledError extends Error {
|
|
|
1416
1802
|
readonly code = "ANALYTICS_NOT_INSTALLED";
|
|
1417
1803
|
/** The surface that was unavailable, for the message a host renders. */
|
|
1418
1804
|
readonly surface: string;
|
|
1419
|
-
|
|
1805
|
+
/**
|
|
1806
|
+
* The server's own ADR-0112 code — the field this branch was chosen BY, when
|
|
1807
|
+
* a producer named one (`NOT_IMPLEMENTED` from the mounted route with no
|
|
1808
|
+
* service behind it, `ROUTE_NOT_FOUND` from the dispatcher when the route is
|
|
1809
|
+
* not mounted at all). Absent for the no-code residual, where a bare
|
|
1810
|
+
* transport 404/501 is all there was to read.
|
|
1811
|
+
*
|
|
1812
|
+
* Additive (objectui#5663): carried so a reader can audit that the headline
|
|
1813
|
+
* and the quoted `detail` below came off the same answer.
|
|
1814
|
+
*/
|
|
1815
|
+
readonly serverCode?: string;
|
|
1816
|
+
constructor(surface: string, detail?: string, serverCode?: string);
|
|
1420
1817
|
}
|
|
1421
1818
|
/** True when `error` is an {@link AnalyticsNotInstalledError} (or its wire twin). */
|
|
1422
1819
|
declare function isAnalyticsNotInstalledError(error: unknown): boolean;
|
|
1820
|
+
/**
|
|
1821
|
+
* Thrown when the dataset route ANSWERED and said the dataset this query named
|
|
1822
|
+
* does not exist in this environment — `404` + ADR-0112 `NOT_FOUND`, the
|
|
1823
|
+
* `body.datasetName` lookup miss in `@objectstack/rest`'s
|
|
1824
|
+
* `registerAnalyticsEndpoints` (`Dataset "<name>" not found.`).
|
|
1825
|
+
*
|
|
1826
|
+
* A SIBLING of {@link AnalyticsNotInstalledError}, never a shade of it
|
|
1827
|
+
* (objectui#5663). Both conditions answer **404** — routes-not-mounted through
|
|
1828
|
+
* the runtime dispatcher's `ROUTE_NOT_FOUND`, an unknown dataset through this
|
|
1829
|
+
* route's own `NOT_FOUND` — so a `res.status === 404` test cannot separate
|
|
1830
|
+
* them, and the mapping that could not tell them apart reported EVERY unknown
|
|
1831
|
+
* dataset as a missing server capability.
|
|
1832
|
+
*
|
|
1833
|
+
* Measured live on a prod tenant: four HotCRM Executive Overview widgets told
|
|
1834
|
+
* the operator to install `@objectstack/service-analytics` and mount
|
|
1835
|
+
* `AnalyticsServicePlugin`, while the analytics service was installed and
|
|
1836
|
+
* answering the whole time. The real condition was an installed
|
|
1837
|
+
* `app.objectstack.hotcrm` pinned at 1.3.0 whose datasets ship in 2.2.2. The
|
|
1838
|
+
* remedy the banner named — install a server plugin — is the opposite corner of
|
|
1839
|
+
* the system from the remedy that works: upgrade the installed app. A wrong
|
|
1840
|
+
* diagnosis is not a smaller version of no diagnosis; it spends the operator's
|
|
1841
|
+
* time in the wrong subsystem.
|
|
1842
|
+
*
|
|
1843
|
+
* The `code` is the ONLY field that separates the two, which is why this branch
|
|
1844
|
+
* reads it and nothing else. See `readAnalyticsErrorEnvelope` below — the
|
|
1845
|
+
* module-private reader that pulls the code out of either declared envelope.
|
|
1846
|
+
*/
|
|
1847
|
+
declare class AnalyticsDatasetNotFoundError extends Error {
|
|
1848
|
+
readonly code = "ANALYTICS_DATASET_NOT_FOUND";
|
|
1849
|
+
/**
|
|
1850
|
+
* The dataset this query asked for, taken from the REQUEST rather than parsed
|
|
1851
|
+
* back out of the server's prose: the request is what we know for certain,
|
|
1852
|
+
* and re-reading the name out of a message would make the headline depend on
|
|
1853
|
+
* that message's WORDING — the coupling framework#5367 spent a whole issue
|
|
1854
|
+
* removing from the producing route.
|
|
1855
|
+
*/
|
|
1856
|
+
readonly datasetName?: string;
|
|
1857
|
+
/** The server's own ADR-0112 code — the field this branch was chosen BY. */
|
|
1858
|
+
readonly serverCode?: string;
|
|
1859
|
+
/** The server's own message, quoted verbatim in the parenthetical. */
|
|
1860
|
+
readonly serverMessage?: string;
|
|
1861
|
+
constructor(opts: {
|
|
1862
|
+
datasetName?: string;
|
|
1863
|
+
serverCode?: string;
|
|
1864
|
+
serverMessage?: string;
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Thrown when the dataset query was refused before it ran because the session
|
|
1869
|
+
* is not authenticated — `401` + ADR-0112 `UNAUTHENTICATED`, the REST seam's
|
|
1870
|
+
* `ANONYMOUS_DENY_BODY` (`@objectstack/core`'s `security/anonymous-deny.ts`,
|
|
1871
|
+
* written verbatim by `enforceAuth`).
|
|
1872
|
+
*
|
|
1873
|
+
* The THIRD branch, and the one the reported card under-states (objectui#5663):
|
|
1874
|
+
* it recorded a live `POST /api/v1/analytics/dataset/query -> 401
|
|
1875
|
+
* UNAUTHENTICATED` and read it as evidence for the dataset-unknown branch. It
|
|
1876
|
+
* is neither of the other two. An expired session reported as "the deployment
|
|
1877
|
+
* is missing a capability" is the SAME defect wearing a different mask — an
|
|
1878
|
+
* operator sent to install a server plugin because their token lapsed — so the
|
|
1879
|
+
* separation is the point, not a nicety.
|
|
1880
|
+
*
|
|
1881
|
+
* Deliberately not left to the generic `Dataset query failed: 401 …` either:
|
|
1882
|
+
* that string names a transport status where a person needs an action, and
|
|
1883
|
+
* "sign in again" is an action.
|
|
1884
|
+
*/
|
|
1885
|
+
declare class AnalyticsUnauthenticatedError extends Error {
|
|
1886
|
+
readonly code = "ANALYTICS_UNAUTHENTICATED";
|
|
1887
|
+
/** The server's own ADR-0112 code — the field this branch was chosen BY. */
|
|
1888
|
+
readonly serverCode?: string;
|
|
1889
|
+
/** The server's own message, quoted verbatim in the parenthetical. */
|
|
1890
|
+
readonly serverMessage?: string;
|
|
1891
|
+
constructor(opts?: {
|
|
1892
|
+
serverCode?: string;
|
|
1893
|
+
serverMessage?: string;
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Thrown when the dataset query was refused because this user may not READ an
|
|
1898
|
+
* object the dataset queries — `403` + ADR-0112 `PERMISSION_DENIED`, which
|
|
1899
|
+
* `@objectstack/service-analytics`' read admission (`readAdmissionDeniedError`)
|
|
1900
|
+
* answers with the same code and status the data API gives the same user for
|
|
1901
|
+
* the same object (objectui#10899).
|
|
1902
|
+
*
|
|
1903
|
+
* The FOURTH branch. It used to fall through to the generic
|
|
1904
|
+
* `Dataset query failed: 403 Forbidden — [Analytics] Access denied: …` string,
|
|
1905
|
+
* which a dashboard tile then rendered verbatim: a raw English exception where
|
|
1906
|
+
* the list view over the same object shows its localized "no access" state.
|
|
1907
|
+
* That generic string names a transport status where a person needs a fact
|
|
1908
|
+
* ("you can't see this data"), and it is the one case a renderer must be able
|
|
1909
|
+
* to tell apart — so it is typed, and it carries `httpStatus` + a `code` the
|
|
1910
|
+
* shared `classifyLoadError` reads as `forbidden` without any string matching.
|
|
1911
|
+
*
|
|
1912
|
+
* `serverMessage` is kept for diagnostics only. The producer deliberately
|
|
1913
|
+
* names the object and nothing else, but it is still platform prose, not text
|
|
1914
|
+
* addressed to the end user.
|
|
1915
|
+
*/
|
|
1916
|
+
declare class AnalyticsForbiddenError extends Error {
|
|
1917
|
+
readonly code = "PERMISSION_DENIED";
|
|
1918
|
+
readonly httpStatus = 403;
|
|
1919
|
+
/** The server's own ADR-0112 code — the field this branch was chosen BY. */
|
|
1920
|
+
readonly serverCode?: string;
|
|
1921
|
+
/** The server's own message, verbatim (diagnostics only). */
|
|
1922
|
+
readonly serverMessage?: string;
|
|
1923
|
+
/** The dataset the refused query asked for, when it named one. */
|
|
1924
|
+
readonly datasetName?: string;
|
|
1925
|
+
constructor(opts?: {
|
|
1926
|
+
serverCode?: string;
|
|
1927
|
+
serverMessage?: string;
|
|
1928
|
+
datasetName?: string;
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1423
1931
|
/**
|
|
1424
1932
|
* Thrown when the server REJECTED the analytics query body (HTTP 400 —
|
|
1425
1933
|
* `VALIDATION_FAILED` since framework#4010 validates `/analytics/query` at the
|
|
@@ -1436,31 +1944,129 @@ declare class AnalyticsQueryRejectedError extends Error {
|
|
|
1436
1944
|
readonly serverCode?: string;
|
|
1437
1945
|
constructor(detail?: string, serverCode?: string);
|
|
1438
1946
|
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Thrown by `aggregate()` when `client.analytics.query` resolves to anything
|
|
1949
|
+
* other than the post-unwrap `AnalyticsResult` — concretely, when the value it
|
|
1950
|
+
* hands back has no `rows` array (objectui#7028).
|
|
1951
|
+
*
|
|
1952
|
+
* ONE SPELLING, BY RULING. `@objectstack/client` 17.3.0 converged
|
|
1953
|
+
* `analytics.query` on `unwrapResponse` (objectstack#13079, maintainer ruling
|
|
1954
|
+
* 2026-08-31, option A): the method resolves to the payload, and the caller
|
|
1955
|
+
* reads `result.rows`. The same ruling ordered this adapter's tolerant row
|
|
1956
|
+
* ladder tightened in the same wave, so that defensive code is not read as a
|
|
1957
|
+
* contract. The ladder also read a bare array, `data` as an array,
|
|
1958
|
+
* `data.data.rows` and `results`; `rows` is now the only spelling read, and
|
|
1959
|
+
* everything else lands here.
|
|
1960
|
+
*
|
|
1961
|
+
* WHY A THROW AND NOT `[]`. The ladder answered every shape it did not
|
|
1962
|
+
* recognise with an empty array, and an empty array is a RESULT: a KPI renders
|
|
1963
|
+
* a confident zero and a chart reads "no data" over a populated table — the
|
|
1964
|
+
* lie objectui#5954 removed on the failure side. The envelope arriving here
|
|
1965
|
+
* means the SDK in front of this call predates the convergence (it is older
|
|
1966
|
+
* than 17.3.0), or something between it and `POST /analytics/query` wraps the
|
|
1967
|
+
* payload a second time. Either way the adapter is handed a contract it no
|
|
1968
|
+
* longer reads, and saying so is the only answer that names the repair.
|
|
1969
|
+
*
|
|
1970
|
+
* WHY NOT THE CLIENT-SIDE FALLBACK. Analytics answered; this is not a
|
|
1971
|
+
* transport failure. Degrading would put plausible numbers from a different
|
|
1972
|
+
* code path over a contract violation — the misdirection
|
|
1973
|
+
* {@link AnalyticsQueryRejectedError} refuses for the same reason
|
|
1974
|
+
* (framework#3878) — so `aggregate()`'s catch rethrows this before it
|
|
1975
|
+
* classifies anything.
|
|
1976
|
+
*/
|
|
1977
|
+
declare class AnalyticsResultShapeError extends Error {
|
|
1978
|
+
readonly code = "ANALYTICS_RESULT_SHAPE_INVALID";
|
|
1979
|
+
/** The object `aggregate()` was called for. */
|
|
1980
|
+
readonly resource: string;
|
|
1981
|
+
/**
|
|
1982
|
+
* True when the value is the envelope the client stopped handing back at
|
|
1983
|
+
* 17.3.0 — `{ success, data: { rows } }`, or its `{ data: { rows } }` core.
|
|
1984
|
+
*/
|
|
1985
|
+
readonly envelope: boolean;
|
|
1986
|
+
/** What the value was, so the producer is identifiable from a log. */
|
|
1987
|
+
readonly received: string;
|
|
1988
|
+
constructor(resource: string, result: unknown);
|
|
1989
|
+
}
|
|
1439
1990
|
/**
|
|
1440
1991
|
* Classify a FAILED analytics call so the caller knows whether to degrade or
|
|
1441
1992
|
* to surface the failure.
|
|
1442
1993
|
*
|
|
1443
1994
|
* `@objectstack/client`'s fetch wrapper throws on a non-2xx, decorating the
|
|
1444
1995
|
* error with the semantic `code` string and the numeric `httpStatus` (the
|
|
1445
|
-
* ADR-0112 / framework#3842 shape this repo already reads elsewhere).
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1996
|
+
* ADR-0112 / framework#3842 shape this repo already reads elsewhere).
|
|
1997
|
+
*
|
|
1998
|
+
* ## The `code` is the contract; the status is a transport fact (objectui#5721)
|
|
1999
|
+
*
|
|
2000
|
+
* What stood here tested `status === 404 || status === 501` BEFORE the code
|
|
2001
|
+
* operands that followed them, so the status short-circuited every one of
|
|
2002
|
+
* them: any 404 on this face was `not-installed` whatever code it carried, and
|
|
2003
|
+
* `NOT_IMPLEMENTED` / `ROUTE_NOT_FOUND` were unreachable for the very
|
|
2004
|
+
* conditions they name. Three unrelated conditions answer 404 on this url —
|
|
2005
|
+
*
|
|
2006
|
+
* route absent 404 `ROUTE_NOT_FOUND` (runtime dispatcher, framework#4019)
|
|
2007
|
+
* cube unknown 404 `CUBE_NOT_FOUND` (service-analytics' inference gate)
|
|
2008
|
+
* object unknown 404 `OBJECT_NOT_FOUND` (the `/data` fallback's own answer)
|
|
2009
|
+
*
|
|
2010
|
+
* — so a misspelled cube read as "this deployment has no analytics", told the
|
|
2011
|
+
* operator to install a server plugin, and re-answered the chart from a
|
|
2012
|
+
* different code path. Same defect as objectui#5663, arrived at from the other
|
|
2013
|
+
* direction: the mapping read a field it did not quote. Branch on the `code`;
|
|
2014
|
+
* consult the status only as a residual, when the answer declared no code at
|
|
2015
|
+
* all. Comparisons go through `errorCodeIs`/`errorCodeIsAnyOf` — the pre- and
|
|
2016
|
+
* post-ADR-0112 spellings both have to match (`@object-ui/types`).
|
|
2017
|
+
*
|
|
2018
|
+
* ### Both envelope families reach this function already decorated (MEASURED)
|
|
2019
|
+
*
|
|
2020
|
+
* `/analytics/query` exits through `@objectstack/runtime`'s
|
|
2021
|
+
* `dispatcher-plugin.errorResponseBase`, i.e. the **wrapped** `{ success:
|
|
2022
|
+
* false, error: { code, message } }` shape — not the flat one the dataset
|
|
2023
|
+
* route writes — and its 401 is `enforceAuth`'s **flat** `ANONYMOUS_DENY_BODY`.
|
|
2024
|
+
* The client's fetch wrapper flattens BOTH before throwing:
|
|
2025
|
+
* `errorBody?.code ?? errorBody?.error?.code` → `error.code`, plus
|
|
2026
|
+
* `error.httpStatus = res.status`. So no envelope reading belongs here; unlike
|
|
2027
|
+
* `queryDataset` (which owns its `fetch` and must read the body itself), this
|
|
2028
|
+
* function is handed the already-flattened error and reads one field.
|
|
2029
|
+
*
|
|
2030
|
+
* ## Each branch names ONE of the three outcomes the caller can take
|
|
2031
|
+
*
|
|
2032
|
+
* - **`not-installed`** — *degrade LOUDLY*. The deployment has no analytics
|
|
2033
|
+
* service: 501 `NOT_IMPLEMENTED` (route mounted, nothing behind it) or 404
|
|
2034
|
+
* `ROUTE_NOT_FOUND` (framework#4019 stops mounting the routes at all).
|
|
2035
|
+
* A client-side aggregate over a scoped `find()` answers the chart
|
|
2036
|
+
* correctly, and the operator is told once that the semantic layer is off.
|
|
2037
|
+
* - **`rejected`** — *THROW*. The server refused OUR body: 400
|
|
2038
|
+
* `VALIDATION_FAILED` (framework#4010 validates `/analytics/query` at the
|
|
2039
|
+
* entry) OR any other coded 400 (objectui#7755 — e.g. `service-analytics`
|
|
2040
|
+
* ships its own 400 `INVALID_FILTER` on a filter shape it refuses). The
|
|
2041
|
+
* status alone is decisive once it is 400: a producer that refuses the
|
|
2042
|
+
* body and ships a code this consumer does not enumerate is refusing it
|
|
2043
|
+
* exactly as hard as one that ships `VALIDATION_FAILED` — the refusal is
|
|
2044
|
+
* in the transport fact, not in which code spells it. Degrading would
|
|
2045
|
+
* answer our own contract violation with plausible numbers from a
|
|
2046
|
+
* different code path (`find()`'s `$filter` accepts array shapes the
|
|
2047
|
+
* analytics body does not) and bury it — the misdirection framework#3878
|
|
2048
|
+
* documented.
|
|
2049
|
+
* - **`unauthenticated`** — *THROW*. 401 `UNAUTHENTICATED`: the request was
|
|
2050
|
+
* refused before it ran, so it is evidence about the SESSION and none at
|
|
2051
|
+
* all about the capability. Degrading is not merely misleading here, it is
|
|
2052
|
+
* futile: the fallback's `find()` carries the same lapsed token and is
|
|
2053
|
+
* refused the same way, so the chart cannot be answered either. "Sign in
|
|
2054
|
+
* again" is an action; "install @objectstack/service-analytics" is not.
|
|
2055
|
+
* - **`cube-not-found`** — *THROW*. 404 `CUBE_NOT_FOUND`
|
|
2056
|
+
* (`analytics-service.assertInferableCube`, framework#3867): analytics IS
|
|
2057
|
+
* installed and answering — the NAME does not exist. That gate throws only
|
|
2058
|
+
* when the name is neither a registered cube nor a registered object, and
|
|
2059
|
+
* the fallback asks `/data/<the same name>`, which objectql's
|
|
2060
|
+
* `assertObjectRegistered` (framework#3770) answers 404 `OBJECT_NOT_FOUND`.
|
|
2061
|
+
* So degrading cannot produce numbers; it can only swap a message that
|
|
2062
|
+
* names the fix ("Define a Cube in your stack, or check the object name")
|
|
2063
|
+
* for a distant one, behind a warning that instructs the wrong repair.
|
|
2064
|
+
* - **`unknown`** — *degrade SILENTLY*. Anything else (5xx, network, a code
|
|
2065
|
+
* this consumer does not know): a transient failure, not a deployment
|
|
2066
|
+
* missing a capability and not a request that named something absent.
|
|
1461
2067
|
*/
|
|
1462
2068
|
declare function classifyAnalyticsFailure(error: unknown): {
|
|
1463
|
-
kind: 'not-installed' | 'rejected' | 'unknown';
|
|
2069
|
+
kind: 'not-installed' | 'rejected' | 'unauthenticated' | 'cube-not-found' | 'unknown';
|
|
1464
2070
|
code?: string;
|
|
1465
2071
|
message?: string;
|
|
1466
2072
|
};
|
|
@@ -1487,16 +2093,48 @@ declare class ConcurrentUpdateError extends Error {
|
|
|
1487
2093
|
readonly httpStatus = 409;
|
|
1488
2094
|
readonly currentVersion: string | null;
|
|
1489
2095
|
readonly currentRecord: unknown;
|
|
2096
|
+
/**
|
|
2097
|
+
* The refusal text the PRODUCER marked as addressed to the end user
|
|
2098
|
+
* (`ApiErrorSchema.userMessage`, objectstack `79c46da90`), or `null` when the
|
|
2099
|
+
* refusal carried no marking.
|
|
2100
|
+
*
|
|
2101
|
+
* Declared on the class because this error has no `details` bag: the shared
|
|
2102
|
+
* reader (`declaredUserMessage` in `@object-ui/react`) looks at the
|
|
2103
|
+
* top-level field first, so parking it here is what makes the marking
|
|
2104
|
+
* readable at the surface. The contract is status-agnostic — a 409 carries
|
|
2105
|
+
* it exactly like a 400 or a 403 does.
|
|
2106
|
+
*/
|
|
2107
|
+
readonly userMessage: string | null;
|
|
1490
2108
|
constructor(opts: {
|
|
1491
2109
|
currentVersion: string | null;
|
|
1492
2110
|
currentRecord: unknown;
|
|
1493
2111
|
message?: string;
|
|
2112
|
+
userMessage?: string | null;
|
|
1494
2113
|
});
|
|
1495
2114
|
}
|
|
1496
2115
|
/**
|
|
1497
2116
|
* Detect "concurrent update" errors raised by the platform. The wire
|
|
1498
2117
|
* shape is `409` + `code: 'CONCURRENT_UPDATE'`. The client surfaces
|
|
1499
2118
|
* extra details on `error.details` (full response body).
|
|
2119
|
+
*
|
|
2120
|
+
* Accepts EITHER that wire `code` OR `name === 'ConcurrentUpdateError'`, and
|
|
2121
|
+
* reads `httpStatus` for neither. This paragraph exists because the doc used
|
|
2122
|
+
* to name only the wire shape, which left the `name` limb reading as drift
|
|
2123
|
+
* (objectui#6375). It is not drift: it is the deliberate cross-realm
|
|
2124
|
+
* duck-check that {@link isViewConfigPermissionDeniedError} documents and
|
|
2125
|
+
* cites *this* function as its precedent for — a host that bundles this
|
|
2126
|
+
* package twice (or re-throws across a worker boundary) ends up holding two
|
|
2127
|
+
* copies of the class, `instanceof` fails, and the `name` string is the only
|
|
2128
|
+
* discriminator left. That host is out of tree by construction, so an in-repo
|
|
2129
|
+
* consumer census cannot see the case the limb was written for and is not
|
|
2130
|
+
* evidence against it; `@object-ui/plugin-form` and `@object-ui/plugin-detail`
|
|
2131
|
+
* each carry their own copy of the same two-limb check for adapters they must
|
|
2132
|
+
* not depend on.
|
|
2133
|
+
*
|
|
2134
|
+
* Deliberately WIDER than {@link normaliseClientError}'s re-wrap, which keys
|
|
2135
|
+
* on the wire `code` alone: an error carrying only the class name is
|
|
2136
|
+
* recognised here and passed through there. Both accepted sets are pinned in
|
|
2137
|
+
* `occ.test.ts`.
|
|
1500
2138
|
*/
|
|
1501
2139
|
declare function isConcurrentUpdateError(error: unknown): error is ConcurrentUpdateError;
|
|
1502
2140
|
/**
|
|
@@ -1509,6 +2147,12 @@ declare function isConcurrentUpdateError(error: unknown): error is ConcurrentUpd
|
|
|
1509
2147
|
* - `400` + `VALIDATION_FAILED` → {@link DataApiValidationError}, carrying the
|
|
1510
2148
|
* server's per-field entries so a form can mark the offending inputs
|
|
1511
2149
|
* instead of showing one undirected toast.
|
|
2150
|
+
*
|
|
2151
|
+
* Both re-wraps preserve the producer's `userMessage` marking (see
|
|
2152
|
+
* {@link liftUserMessage}). Re-wrapping used to drop it, which made the
|
|
2153
|
+
* marking unreadable at every surface downstream of this boundary — nothing
|
|
2154
|
+
* threw, the typed error was otherwise correct, and the user simply got a
|
|
2155
|
+
* generic string.
|
|
1512
2156
|
*/
|
|
1513
2157
|
declare function normaliseClientError(error: unknown): unknown;
|
|
1514
2158
|
/**
|
|
@@ -1586,20 +2230,295 @@ type ConnectionStateListener = (event: ConnectionStateEvent) => void;
|
|
|
1586
2230
|
*/
|
|
1587
2231
|
type BatchProgressListener = (event: BatchProgressEvent) => void;
|
|
1588
2232
|
|
|
2233
|
+
/**
|
|
2234
|
+
* The `reason` of a write-strip this bundle's spec pin cannot name
|
|
2235
|
+
* (objectui#4934).
|
|
2236
|
+
*
|
|
2237
|
+
* Deliberately NOT a spec spelling: it is namespaced so it can never collide
|
|
2238
|
+
* with an arm `@objectstack/spec` adds later (a collision would merge real
|
|
2239
|
+
* reasons into the skew bucket — the failure this whole card is about, one level
|
|
2240
|
+
* up). `droppedFieldsReason.boundary.test.ts` pins that the installed spec does
|
|
2241
|
+
* not declare it.
|
|
2242
|
+
*/
|
|
2243
|
+
declare const UNRECOGNIZED_DROP_REASON = "objectui:unrecognized-drop-reason";
|
|
2244
|
+
/**
|
|
2245
|
+
* The skew arm: one server-reported write-strip whose `reason` is outside the
|
|
2246
|
+
* enum this bundle's `@objectstack/spec` pin declares (objectui#4934).
|
|
2247
|
+
*
|
|
2248
|
+
* A deployed client normally runs BEHIND the server it talks to, so a reason
|
|
2249
|
+
* from the future is the expected skew direction, not a corrupt payload. The
|
|
2250
|
+
* boundary used to assert such an entry into {@link DroppedFieldsEvent} on shape
|
|
2251
|
+
* alone — `reason` was never read — so the interior was typed to trust a union
|
|
2252
|
+
* nothing had checked, and the next consumer to write an exhaustive-looking
|
|
2253
|
+
* table over `DroppedFieldsEvent['reason']` would have been handed a value that
|
|
2254
|
+
* type says is impossible.
|
|
2255
|
+
*
|
|
2256
|
+
* Three properties of this shape are load-bearing:
|
|
2257
|
+
*
|
|
2258
|
+
* - The entry is **kept**. Dropping it would tell the user nothing about fields
|
|
2259
|
+
* the server really did strip — precisely the silence objectui#3484 removed.
|
|
2260
|
+
* - The wire value is **preserved verbatim** in {@link unrecognizedReason},
|
|
2261
|
+
* never coerced or normalised onto a known arm: claiming `readonly` for a
|
|
2262
|
+
* reason we cannot name is a false statement about the user's data, and it is
|
|
2263
|
+
* also unfalsifiable once the original value is gone.
|
|
2264
|
+
* - `reason` carries {@link UNRECOGNIZED_DROP_REASON}, which is not assignable
|
|
2265
|
+
* to `DroppedFieldsEvent['reason']`. That is what makes the skew case visible
|
|
2266
|
+
* to `tsc` at every consumer instead of resting on N per-consumer
|
|
2267
|
+
* disciplines — and it keeps the spec type as the canonical arm rather than
|
|
2268
|
+
* widening the whole surface to `string` (objectui#3160).
|
|
2269
|
+
*/
|
|
2270
|
+
interface UnrecognizedDropReasonEvent {
|
|
2271
|
+
object?: string;
|
|
2272
|
+
fields: string[];
|
|
2273
|
+
reason: typeof UNRECOGNIZED_DROP_REASON;
|
|
2274
|
+
/** Whatever the server sent, untouched — including a non-string or nothing at all. */
|
|
2275
|
+
unrecognizedReason: unknown;
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* One entry of a write-warning: either the spec type (canonical arm) or the
|
|
2279
|
+
* named skew arm above. Narrow with `entry.reason === UNRECOGNIZED_DROP_REASON`.
|
|
2280
|
+
*/
|
|
2281
|
+
type DroppedFieldsNotice = DroppedFieldsEvent | UnrecognizedDropReasonEvent;
|
|
1589
2282
|
/**
|
|
1590
2283
|
* Emitted after a create/update whose response carried `droppedFields`
|
|
1591
|
-
* (
|
|
2284
|
+
* (objectstack-ai/objectstack#3431 / objectstack-ai/objectstack#3455). The write SUCCEEDED — this is a warning that some
|
|
1592
2285
|
* supplied fields never landed, so the UI can tell the user rather than let it
|
|
1593
2286
|
* pass silently. Subscribe via {@link ObjectStackAdapter.onWriteWarning}.
|
|
2287
|
+
*
|
|
2288
|
+
* `droppedFields` is the two-arm {@link DroppedFieldsNotice} and not
|
|
2289
|
+
* `DroppedFieldsEvent[]`: the wire is parsed here, and an entry whose `reason`
|
|
2290
|
+
* this bundle's spec pin cannot name arrives on the explicit skew arm rather
|
|
2291
|
+
* than being asserted into the union (objectui#4934).
|
|
1594
2292
|
*/
|
|
1595
2293
|
interface WriteWarningEvent {
|
|
1596
2294
|
operation: 'create' | 'update';
|
|
1597
2295
|
resource: string;
|
|
1598
2296
|
id?: string | number;
|
|
1599
|
-
droppedFields:
|
|
2297
|
+
droppedFields: DroppedFieldsNotice[];
|
|
1600
2298
|
}
|
|
1601
2299
|
/** Event listener type for write-warning (dropped-fields) events. */
|
|
1602
2300
|
type WriteWarningListener = (event: WriteWarningEvent) => void;
|
|
2301
|
+
/**
|
|
2302
|
+
* What a FAILED `listImportMappings` read actually was (objectui#7741).
|
|
2303
|
+
*
|
|
2304
|
+
* - `not-served` — this deployment does not serve the `mapping` kind, or has
|
|
2305
|
+
* no `/meta` door at all. QUIET: a real, supported deployment shape, and the
|
|
2306
|
+
* empty list plus a hidden selector is the correct rendering of it.
|
|
2307
|
+
* - `refused` — the server answered and declined THIS caller: anonymous,
|
|
2308
|
+
* lapsed token, missing permission, method withheld. LOUD, and the remedy
|
|
2309
|
+
* names a person: sign in again, or ask for the grant.
|
|
2310
|
+
* - `unreadable` — the read could not be completed for any other reason: a
|
|
2311
|
+
* 5xx, a network failure, a code this consumer cannot name. LOUD, and the
|
|
2312
|
+
* remedy is to retry or report.
|
|
2313
|
+
*
|
|
2314
|
+
* The last two are the same verdict for the user — *we could not find out* —
|
|
2315
|
+
* and they are separated because the sentence that helps differs. What they
|
|
2316
|
+
* share is what matters: neither is evidence that no mapping is registered.
|
|
2317
|
+
*
|
|
2318
|
+
* ⚠️ The three NAMES are shared by every classifier feeding
|
|
2319
|
+
* {@link MetadataReadWarningEvent}; the POPULATION each name covers is not, and
|
|
2320
|
+
* is decided per read door. `not-served` in particular is a strictly smaller
|
|
2321
|
+
* set on `view` than on `mapping` — see {@link classifyViewsFailure}.
|
|
2322
|
+
*/
|
|
2323
|
+
type MetadataReadFailureKind = 'not-served' | 'refused' | 'unreadable';
|
|
2324
|
+
/**
|
|
2325
|
+
* The published spelling of {@link MetadataReadFailureKind}, kept as the name
|
|
2326
|
+
* objectui#7741 shipped (`@object-ui/data-objectstack@17.2.0`).
|
|
2327
|
+
*
|
|
2328
|
+
* An ALIAS, not a second declaration: identical members, so every existing
|
|
2329
|
+
* consumer's assignability is unchanged in both directions. It was renamed
|
|
2330
|
+
* because the channel gained a second emitter (objectui#8151) and a type called
|
|
2331
|
+
* `ImportMappingsFailureKind` describing a `view` read would be a name that
|
|
2332
|
+
* lies; the old name stays because retiring a published one is not this card's
|
|
2333
|
+
* business.
|
|
2334
|
+
*/
|
|
2335
|
+
type ImportMappingsFailureKind = MetadataReadFailureKind;
|
|
2336
|
+
/**
|
|
2337
|
+
* Classify a FAILED `meta.getItems('mapping')` call so the caller knows whether
|
|
2338
|
+
* to stay quiet or to say something (objectui#7741).
|
|
2339
|
+
*
|
|
2340
|
+
* ## Why this function exists
|
|
2341
|
+
*
|
|
2342
|
+
* `listImportMappings` degrades every failure to `[]`, and the wizard hides its
|
|
2343
|
+
* saved-mapping selector on an empty list. So "the server served zero mappings"
|
|
2344
|
+
* and "the server never answered" rendered identically — the same UI, on every
|
|
2345
|
+
* deployment, with a `console.warn` as the only discriminator. That silence did
|
|
2346
|
+
* not merely hide a fault: it produced a confident WRONG diagnosis in a careful
|
|
2347
|
+
* reporter (objectstack#14026 was filed, routed and worked by two seats against
|
|
2348
|
+
* a repo whose wizard had been correct since `@object-ui/data-objectstack`
|
|
2349
|
+
* 17.1.0), and the misdiagnosis travelled further than the fault would have.
|
|
2350
|
+
*
|
|
2351
|
+
* This is the discrimination framework objectstack#13906 decision 1 option A already
|
|
2352
|
+
* adopted at the tenancy-posture seam — *a thing that could not be READ is not
|
|
2353
|
+
* a thing that is ABSENT* — applied here. It is not a new principle.
|
|
2354
|
+
*
|
|
2355
|
+
* ## It reads the ERROR, never the result
|
|
2356
|
+
*
|
|
2357
|
+
* The one thing this must never do is decide from "is the result an empty
|
|
2358
|
+
* array". An empty array is what BOTH conditions produce, so a test on it can
|
|
2359
|
+
* never fail and can never separate *served zero* from *did not serve* — a
|
|
2360
|
+
* reading that cannot fail is indistinguishable from a reading that passed.
|
|
2361
|
+
* Every branch below reads `err` itself: the ADR-0112 `code` first (the
|
|
2362
|
+
* contract), the status only where no code was declared (a transport fact many
|
|
2363
|
+
* conditions share). Same rule, same order, and for the same reasons as
|
|
2364
|
+
* {@link classifyAnalyticsFailure} (objectui#5663 / objectui#5721).
|
|
2365
|
+
*
|
|
2366
|
+
* `@objectstack/client`'s fetch wrapper hands both envelope families here
|
|
2367
|
+
* already flattened — `errorBody?.code ?? errorBody?.error?.code` onto
|
|
2368
|
+
* `error.code`, plus `error.httpStatus = res.status` — so no envelope reading
|
|
2369
|
+
* belongs in this function. Comparisons go through `errorCodeIs`/
|
|
2370
|
+
* `errorCodeIsAnyOf` so the pre- and post-ADR-0112 spellings both match.
|
|
2371
|
+
*/
|
|
2372
|
+
declare function classifyImportMappingsFailure(error: unknown): {
|
|
2373
|
+
kind: ImportMappingsFailureKind;
|
|
2374
|
+
code?: string;
|
|
2375
|
+
status?: number;
|
|
2376
|
+
message?: string;
|
|
2377
|
+
};
|
|
2378
|
+
/**
|
|
2379
|
+
* Classify a FAILED `view` metadata list read so {@link ObjectStackAdapter.listViews}
|
|
2380
|
+
* knows whether to stay quiet or to say something (objectui#8151).
|
|
2381
|
+
*
|
|
2382
|
+
* ## Why this is a SEPARATE reading and not a second caller of
|
|
2383
|
+
* {@link classifyImportMappingsFailure}
|
|
2384
|
+
*
|
|
2385
|
+
* The two methods share a defect and a remedy, not a population. The quiet arm
|
|
2386
|
+
* `listImportMappings` is built around — *an older server that does not serve
|
|
2387
|
+
* this kind* — has no members on `view`, and carrying it over would put a fresh
|
|
2388
|
+
* swallow into the method this card exists to un-swallow. Measured, on the
|
|
2389
|
+
* framework tree, three ways:
|
|
2390
|
+
*
|
|
2391
|
+
* 1. **`view` is in the platform's static spelling contract**, so the metadata
|
|
2392
|
+
* LIST door's unknown-kind refusal is unreachable for it.
|
|
2393
|
+
* `RestServer.refuseUnknownMetaListType` (framework#9488,
|
|
2394
|
+
* `packages/rest/src/rest-server.ts`) returns without writing a refusal
|
|
2395
|
+
* whenever `unrecognisedMetaTypeRefusal(urlType)` is `null`, and that
|
|
2396
|
+
* predicate answers `null` for every spelling in the contract.
|
|
2397
|
+
* `packages/spec/src/meta-spelling/meta-url-data.generated.ts` carries both
|
|
2398
|
+
* `"views": "view"` and the canonical singular `view`. So a `400`
|
|
2399
|
+
* `INVALID_REQUEST` on `GET /meta/view` is NEVER that door saying "this
|
|
2400
|
+
* deployment carries no such kind" — it is some other refusal of the
|
|
2401
|
+
* request, and reading it as kind-absence would silence a real one.
|
|
2402
|
+
* 2. **There is no "before" for `view` to be older than.** `mapping`'s quiet
|
|
2403
|
+
* arm exists because `mapping` was PROMOTED into the declared set
|
|
2404
|
+
* (framework#2611), so servers predating the promotion are a real, shipped
|
|
2405
|
+
* population. `view` is the kind the metadata surface is built around — the
|
|
2406
|
+
* compound-arity door `/meta/<object>/views/<name>`, the ADR-0017
|
|
2407
|
+
* `ViewItem` discriminant this very method filters on.
|
|
2408
|
+
* 3. **A deployment that cannot serve `view` cannot render the caller.**
|
|
2409
|
+
* `listViews` is reached only from `ObjectView`, whose `objectDef` came from
|
|
2410
|
+
* `MetadataProvider`, which reads the SAME door and lists `view` among its
|
|
2411
|
+
* `EAGER_TYPES` at mount (`packages/app-shell/src/providers/MetadataProvider.tsx`).
|
|
2412
|
+
* A server that does not answer `/meta/view` has already failed that read
|
|
2413
|
+
* before any object page exists.
|
|
2414
|
+
*
|
|
2415
|
+
* ## So the quiet set here is strictly SMALLER — it is the DOORLESS one only
|
|
2416
|
+
*
|
|
2417
|
+
* What survives as quiet is not about `view` at all: it is *this host mounted no
|
|
2418
|
+
* metadata door*. That stays silent for the reason objectui#7741 gave — a real,
|
|
2419
|
+
* supported deployment shape must not be turned into a visible fault — and it
|
|
2420
|
+
* costs nothing to keep silent here, because such a host has already failed
|
|
2421
|
+
* `MetadataProvider`'s eager `app`/`object`/`view` reads; a per-object toast
|
|
2422
|
+
* would be a fourth voice on one deployment fact, not a new one.
|
|
2423
|
+
*
|
|
2424
|
+
* Everything else is LOUD. In particular a `refused` (401/403/405,
|
|
2425
|
+
* `UNAUTHENTICATED`, `PERMISSION_DENIED`) is the condition this card was filed
|
|
2426
|
+
* for: a token that lapsed mid-session renders the object's view switcher as
|
|
2427
|
+
* though the user's own saved views did not exist.
|
|
2428
|
+
*
|
|
2429
|
+
* ## It reads the ERROR, never the result — and from BOTH of this method's doors
|
|
2430
|
+
*
|
|
2431
|
+
* `listViews` is fed by two transports, unlike its sibling's one, and they
|
|
2432
|
+
* decorate differently:
|
|
2433
|
+
*
|
|
2434
|
+
* published `client.meta.getItems('view')` -> `@objectstack/client`'s fetch
|
|
2435
|
+
* wrapper: `error.code` (flattened from either envelope family)
|
|
2436
|
+
* plus `error.httpStatus`.
|
|
2437
|
+
* drafts `MetadataClient.withPreviewDrafts(true).list('view')` (ADR-0037,
|
|
2438
|
+
* `?preview=draft`) -> this package's own `parseError`:
|
|
2439
|
+
* `err.code` plus **`err.status`**, with no `httpStatus` at all.
|
|
2440
|
+
*
|
|
2441
|
+
* The status ladder below therefore reads `httpStatus`, then `status`, then
|
|
2442
|
+
* `statusCode`, and is the reason a preview-mode failure classifies the same as
|
|
2443
|
+
* a published-mode one instead of falling through to the code-less residual.
|
|
2444
|
+
* What it must never read is "is the result an empty array": that is what BOTH
|
|
2445
|
+
* a served-zero and a refusal produce, so a test on it can never fail for the
|
|
2446
|
+
* condition it is supposed to be about. This is framework objectstack#13906 decision 1
|
|
2447
|
+
* option A — *a thing that could not be READ is not a thing that is ABSENT*.
|
|
2448
|
+
*/
|
|
2449
|
+
declare function classifyViewsFailure(error: unknown): {
|
|
2450
|
+
kind: MetadataReadFailureKind;
|
|
2451
|
+
code?: string;
|
|
2452
|
+
status?: number;
|
|
2453
|
+
message?: string;
|
|
2454
|
+
};
|
|
2455
|
+
/**
|
|
2456
|
+
* Emitted when a metadata READ was answered by the server with a failure that
|
|
2457
|
+
* is NOT the supported "this deployment does not serve that kind" shape, and
|
|
2458
|
+
* the adapter degraded it to an empty result anyway (objectui#7741).
|
|
2459
|
+
*
|
|
2460
|
+
* The degrade is deliberate and unchanged — the caller still receives `[]`, and
|
|
2461
|
+
* `listImportMappings`' published return type has not moved. This event is the
|
|
2462
|
+
* channel ALONGSIDE it: the fact that the empty result is an artefact of a
|
|
2463
|
+
* failed read, carried in a form a consumer can act on, instead of a
|
|
2464
|
+
* `console.warn` nothing in the UI points at.
|
|
2465
|
+
*
|
|
2466
|
+
* Subscribe via {@link ObjectStackAdapter.onMetadataReadWarning}. A sibling of
|
|
2467
|
+
* {@link WriteWarningEvent} and `MetadataSaveAdvisoryEvent` in SHAPE — one
|
|
2468
|
+
* long-lived instance, `subscribe → unsubscribe`, `AdapterProvider` wires it
|
|
2469
|
+
* once — and deliberately not a payload pushed down either of those: both of
|
|
2470
|
+
* them describe a write that SUCCEEDED, so carrying a failed read on one would
|
|
2471
|
+
* make the event lie about what happened.
|
|
2472
|
+
*
|
|
2473
|
+
* `operation` and `kind` are CLOSED unions on purpose. Every emitter is named
|
|
2474
|
+
* in them, so widening is an additive, reviewed change rather than something a
|
|
2475
|
+
* consumer's exhaustive switch discovers at runtime. (The same trade
|
|
2476
|
+
* `WriteWarningEvent`'s required `operation` documents, taken deliberately
|
|
2477
|
+
* here.)
|
|
2478
|
+
*
|
|
2479
|
+
* ## The widening this design was built for happened (objectui#8151)
|
|
2480
|
+
*
|
|
2481
|
+
* objectui#7741 shipped both fields as SINGLE-member unions and said in as many
|
|
2482
|
+
* words that a second emitter should arrive as a reviewed widening. It has:
|
|
2483
|
+
* `listViews` is the second, for the same defect one method over, and the
|
|
2484
|
+
* mechanism worked as designed — the consumer that renders these events
|
|
2485
|
+
* (`app-shell`'s `metadataReadWarningToast`) had a `switch` that named the one
|
|
2486
|
+
* operation, so adding the member turned "a views failure is toasted as an
|
|
2487
|
+
* import-mapping failure" into a COMPILE error instead of a runtime lie.
|
|
2488
|
+
*
|
|
2489
|
+
* ⚠️ The pair is the emitter's invariant, not the type's: `operation` and
|
|
2490
|
+
* `kind` are two independent unions, so nothing in the type stops
|
|
2491
|
+
* `{ operation: 'listViews', kind: 'mapping' }` being constructed. Each emitter
|
|
2492
|
+
* writes its own pair, one line apart, and every consumer branches on
|
|
2493
|
+
* `operation` — the adapter METHOD, which is what names the list the user is
|
|
2494
|
+
* actually looking at. Tightening this to a discriminated union would mean
|
|
2495
|
+
* turning a published `interface` into a type alias; that is a reviewable
|
|
2496
|
+
* change on its own terms and is deliberately not smuggled in here.
|
|
2497
|
+
*/
|
|
2498
|
+
interface MetadataReadWarningEvent {
|
|
2499
|
+
/** The adapter method whose read failed. */
|
|
2500
|
+
operation: 'listImportMappings' | 'listViews';
|
|
2501
|
+
/** The metadata kind it asked for. */
|
|
2502
|
+
kind: 'mapping' | 'view';
|
|
2503
|
+
/** The object the read was scoped to. */
|
|
2504
|
+
objectName: string;
|
|
2505
|
+
/**
|
|
2506
|
+
* Which loud verdict this is — see {@link MetadataReadFailureKind}. Never
|
|
2507
|
+
* `'not-served'`: that arm is the supported deployment shape and is not
|
|
2508
|
+
* emitted at all, so a subscriber never has to filter it out. ⚠️ WHICH
|
|
2509
|
+
* failures fall in that un-emitted arm differs per `operation` — it is a
|
|
2510
|
+
* strictly smaller set on `listViews` (see {@link classifyViewsFailure}).
|
|
2511
|
+
*/
|
|
2512
|
+
reason: Exclude<MetadataReadFailureKind, 'not-served'>;
|
|
2513
|
+
/** The server's own ADR-0112 code, when it declared one. */
|
|
2514
|
+
code?: string;
|
|
2515
|
+
/** The HTTP status, when the failure carried one. */
|
|
2516
|
+
status?: number;
|
|
2517
|
+
/** The server's own message, when it sent one. Rendered verbatim, not translated. */
|
|
2518
|
+
message?: string;
|
|
2519
|
+
}
|
|
2520
|
+
/** Event listener type for metadata read-warning events. */
|
|
2521
|
+
type MetadataReadWarningListener = (event: MetadataReadWarningEvent) => void;
|
|
1603
2522
|
|
|
1604
2523
|
/**
|
|
1605
2524
|
* Resolve which object a `type='view'` metadata item belongs to.
|
|
@@ -1619,8 +2538,17 @@ type WriteWarningListener = (event: WriteWarningEvent) => void;
|
|
|
1619
2538
|
*
|
|
1620
2539
|
* `object` is the identity field the write path stamps (and that the
|
|
1621
2540
|
* framework's overlay heals onto identity-less personalization rows —
|
|
1622
|
-
* objectstack#2555); `data.object` is the config's data-provider target
|
|
1623
|
-
*
|
|
2541
|
+
* objectstack#2555); `data.object` is the config's data-provider target.
|
|
2542
|
+
*
|
|
2543
|
+
* ⛔ No `objectName` leg (objectui#11013, ruling 甲 on objectstack#20051:
|
|
2544
|
+
* objectui reads a stored view row by the spec's declared spellings). The
|
|
2545
|
+
* spec declares `object` on every `view` member — required on the ViewItem
|
|
2546
|
+
* record and on both flattened overlays — and declares `objectName` on none,
|
|
2547
|
+
* so the metadata write door refuses a row that carries only `objectName`
|
|
2548
|
+
* (`viewItemObjectName.declaredSpelling-11013.test.ts` pins that door beside
|
|
2549
|
+
* this reader). Every write path in this package stamps `object` as well, so a
|
|
2550
|
+
* row the console wrote carries it; a row that carries ONLY the undeclared
|
|
2551
|
+
* spelling no longer matches any object here.
|
|
1624
2552
|
*
|
|
1625
2553
|
* **Exported** since objectui#4373, for the same one-spelling reason: a writer
|
|
1626
2554
|
* outside this module that holds a view BODY but not its object name (app-shell's
|
|
@@ -1630,6 +2558,89 @@ type WriteWarningListener = (event: WriteWarningEvent) => void;
|
|
|
1630
2558
|
* prevent. Identity only — it names no cache key.
|
|
1631
2559
|
*/
|
|
1632
2560
|
declare function viewItemObjectName(item: any): string | undefined;
|
|
2561
|
+
/**
|
|
2562
|
+
* The keys a personalization overlay row legitimately OWNS (objectui#5233).
|
|
2563
|
+
*
|
|
2564
|
+
* One per `persistViewPatch` call site in app-shell's `ObjectView` — the ONLY
|
|
2565
|
+
* production writer of these rows — read off the tree rather than recalled:
|
|
2566
|
+
* `rowHeight` (the density toggle, spec-canonical since #2890), `sort`,
|
|
2567
|
+
* `hiddenFields`, `columnState` and `inlineEdit`. Nothing else in such a row
|
|
2568
|
+
* is an opinion the user expressed; anything else it carries is a COPY of the
|
|
2569
|
+
* source view as it stood at write time, because `persistViewPatch` USED TO
|
|
2570
|
+
* send `{ ...baseViewDef, ...patch }` and this adapter persists what it is
|
|
2571
|
+
* given.
|
|
2572
|
+
*
|
|
2573
|
+
* That copy was the defect the maintainer ruled on (objectstack#7494, comment
|
|
2574
|
+
* 5261754173): an overlay written by a mere column drag froze the view's
|
|
2575
|
+
* effective `filter` — and its `columns`, `label`, `type`, `isDefault` … — as
|
|
2576
|
+
* of that moment, and because the display merge is `{ ...source, ...override }`
|
|
2577
|
+
* the frozen copy SHADOWED the source view forever. An admin then edited the
|
|
2578
|
+
* view's filter and every user who once resized a column kept the old one,
|
|
2579
|
+
* with nothing anywhere reporting it.
|
|
2580
|
+
*
|
|
2581
|
+
* Both halves of that ruling have now landed, and this adapter's behaviour is
|
|
2582
|
+
* unchanged by either — it still persists what it is given:
|
|
2583
|
+
*
|
|
2584
|
+
* - **read** (PR #5272, {@link narrowPersonalizationOverlay}): the consumer
|
|
2585
|
+
* that MERGES an overlay over a source view contributes only these keys, so
|
|
2586
|
+
* every already-stored fat row that carries the marker stops shadowing its
|
|
2587
|
+
* source. A fat row written before the marker is no longer narrowed
|
|
2588
|
+
* (objectui#10210, ruling B — see {@link isPersonalizationOverlayRow}).
|
|
2589
|
+
* - **write** (objectui#5233, `buildPersistedViewBody` in app-shell's
|
|
2590
|
+
* `ObjectView`, unblocked by `columnState`'s admission to the view-metadata
|
|
2591
|
+
* surface as a runtime-only overlay key — objectstack `d5552ca13`, released in
|
|
2592
|
+
* `@objectstack/spec` 17.1.0): a *system view's* overlay is now written as
|
|
2593
|
+
* the patch alone, so no new row freezes anything, and because the write is
|
|
2594
|
+
* a whole-document PUT the next toggle also strips an old fat row. A *saved
|
|
2595
|
+
* view's* own row is deliberately still written whole — for it the body IS
|
|
2596
|
+
* the view, not a copy of one.
|
|
2597
|
+
*
|
|
2598
|
+
* A fat row is therefore a legacy shape, not a shape this product still
|
|
2599
|
+
* produces; the list below is still what a reader is allowed to trust from one.
|
|
2600
|
+
*
|
|
2601
|
+
* ⛔ Do not grow this list to make some other key "stick" through an overlay.
|
|
2602
|
+
* A key that belongs to the view belongs in the view; the overlay is a patch,
|
|
2603
|
+
* and a patch that carries the whole document is what this list exists to
|
|
2604
|
+
* stop. Adding a sixth entry is only correct alongside a sixth
|
|
2605
|
+
* `persistViewPatch` call site — {@link narrowPersonalizationOverlay} is what
|
|
2606
|
+
* a reader checks that against.
|
|
2607
|
+
*/
|
|
2608
|
+
declare const VIEW_OVERLAY_OWNED_KEYS: readonly ["rowHeight", "sort", "hiddenFields", "columnState", "inlineEdit"];
|
|
2609
|
+
/**
|
|
2610
|
+
* Reduce a personalization overlay row to the keys it owns — the read-side
|
|
2611
|
+
* half of objectui#5233, and the half that reaches rows ALREADY STORED.
|
|
2612
|
+
*
|
|
2613
|
+
* Rows written before this shipped carry the whole source view (see
|
|
2614
|
+
* {@link VIEW_OVERLAY_OWNED_KEYS}). Of the three dispositions the issue names
|
|
2615
|
+
* for them — strip on next write, migrate, tolerate on read — this is the
|
|
2616
|
+
* third, chosen deliberately and stated here rather than left implicit,
|
|
2617
|
+
* because it is the only one that is already true for every existing marked
|
|
2618
|
+
* row the moment it ships: strip-on-next-write heals a row only when its user happens
|
|
2619
|
+
* to touch that view again (and leaves the frozen filter live until then),
|
|
2620
|
+
* and a migration needs a runner this product does not have for `sys_metadata`
|
|
2621
|
+
* rows an operator may not even know exist. What the issue forbids is SILENT
|
|
2622
|
+
* tolerance; this is the explicit, pinned kind (`viewOverlayPatchOnly.test.ts`
|
|
2623
|
+
* in this package, `ObjectView.overlayPatchOnly.test.tsx` in app-shell).
|
|
2624
|
+
*
|
|
2625
|
+
* Applied by the consumer that MERGES an override over a source view
|
|
2626
|
+
* (app-shell's `sanitizeViewOverride`, the one seam both of
|
|
2627
|
+
* `loadViewOverrides`' read branches pass through), NOT by
|
|
2628
|
+
* {@link ObjectStackAdapter.listViewOverrides} / {@link ObjectStackAdapter.getView}:
|
|
2629
|
+
* those two answer with the stored DOCUMENT, their equality is itself pinned
|
|
2630
|
+
* ("same key space, same document" — `listViewOverrides.test.ts`), and
|
|
2631
|
+
* `InterfaceListPage` hydrates a hollow view out of that document. Narrowing
|
|
2632
|
+
* belongs where a row is read AS A PATCH, not where it is read as a row.
|
|
2633
|
+
*
|
|
2634
|
+
* Non-overlay rows — a genuine saved view's own body, which the same batch
|
|
2635
|
+
* read enumerates — are returned by REFERENCE, untouched: for those the row
|
|
2636
|
+
* IS the view, and every key on it is an opinion its author expressed.
|
|
2637
|
+
* Classification is {@link isPersonalizationOverlayRow}, the same predicate
|
|
2638
|
+
* {@link ObjectStackAdapter.listViews} excludes rows by, so a row cannot be a
|
|
2639
|
+
* saved view for one reader and an overlay for the other. It reads the marker
|
|
2640
|
+
* only (objectui#10210, ruling B): an unmarked row is returned by reference
|
|
2641
|
+
* whatever its shape, including a fat row written before the marker existed.
|
|
2642
|
+
*/
|
|
2643
|
+
declare function narrowPersonalizationOverlay<T>(row: T): T;
|
|
1633
2644
|
|
|
1634
2645
|
/**
|
|
1635
2646
|
* ObjectStack Data Source Adapter
|
|
@@ -1684,6 +2695,8 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1684
2695
|
private mutationListeners;
|
|
1685
2696
|
private writeWarningListeners;
|
|
1686
2697
|
private saveAdvisoryListeners;
|
|
2698
|
+
private metadataReadWarningListeners;
|
|
2699
|
+
private systemCapabilities;
|
|
1687
2700
|
constructor(config: {
|
|
1688
2701
|
baseUrl: string;
|
|
1689
2702
|
token?: string;
|
|
@@ -1695,6 +2708,13 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1695
2708
|
autoReconnect?: boolean;
|
|
1696
2709
|
maxReconnectAttempts?: number;
|
|
1697
2710
|
reconnectDelay?: number;
|
|
2711
|
+
/**
|
|
2712
|
+
* [ADR-0066] The session's system capabilities, when the host already has
|
|
2713
|
+
* them at construction time. Most hosts do NOT — `/me/permissions`
|
|
2714
|
+
* resolves after the adapter exists — and push them in later via
|
|
2715
|
+
* {@link ObjectStackAdapter.setSystemCapabilities}. Omit for "unreported".
|
|
2716
|
+
*/
|
|
2717
|
+
systemCapabilities?: string[];
|
|
1698
2718
|
});
|
|
1699
2719
|
/**
|
|
1700
2720
|
* Ensure the client is connected to the server.
|
|
@@ -1743,7 +2763,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1743
2763
|
* — a per-object metadata-driven search (ADR-0061) — this consults the search
|
|
1744
2764
|
* index and ranks hits across objects, so it surfaces records the per-object
|
|
1745
2765
|
* fanout misses. Global affordances (⌘K command palette, search page) prefer
|
|
1746
|
-
* this path (
|
|
2766
|
+
* this path (objectstack-ai/objectstack#3371).
|
|
1747
2767
|
*
|
|
1748
2768
|
* Returns `{ query, hits }`. A backend without the search plugin installed
|
|
1749
2769
|
* answers `404`; we treat that as "no global search here" and return an empty
|
|
@@ -1779,14 +2799,20 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1779
2799
|
*/
|
|
1780
2800
|
private emitWriteWarning;
|
|
1781
2801
|
/**
|
|
1782
|
-
* Read `droppedFields` off a create/update response (
|
|
2802
|
+
* Read `droppedFields` off a create/update response (objectstack-ai/objectstack#3431 / objectstack-ai/objectstack#3455)
|
|
1783
2803
|
* and, when present, notify write-warning subscribers. Tolerant of a client
|
|
1784
2804
|
* whose response type predates `droppedFields`: the field is read structurally
|
|
1785
2805
|
* and validated, so an older client (or a backend that never drops) is a no-op.
|
|
2806
|
+
*
|
|
2807
|
+
* SHAPE decides whether an entry is an event at all (it must name at least one
|
|
2808
|
+
* field); `object`, `fields` and `reason` are then PARSED — an unrecognized
|
|
2809
|
+
* reason routed to the skew arm, a non-string field element refused, a missing
|
|
2810
|
+
* `object` healed from `resource` — never asserted into the union, and the
|
|
2811
|
+
* entry itself never dropped for them (objectui#4934, `f75810e7c`).
|
|
1786
2812
|
*/
|
|
1787
2813
|
private notifyDroppedFields;
|
|
1788
2814
|
/**
|
|
1789
|
-
* Same, for the cross-object transactional batch (
|
|
2815
|
+
* Same, for the cross-object transactional batch (objectstack-ai/objectstack#3794). Its
|
|
1790
2816
|
* response hangs the events off a top-level `droppedFields` list, each tagged
|
|
1791
2817
|
* with the `index` of the operation it came from — `results` entries are bare
|
|
1792
2818
|
* record echoes with nowhere to hang a per-row list.
|
|
@@ -1800,7 +2826,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1800
2826
|
private notifyBatchDroppedFields;
|
|
1801
2827
|
/**
|
|
1802
2828
|
* Subscribe to write-warning events (a create/update dropped caller-supplied
|
|
1803
|
-
* fields — #3431
|
|
2829
|
+
* fields — objectstack-ai/objectstack#3431 / objectstack-ai/objectstack#3455). Returns an unsubscribe function. The app shell uses
|
|
1804
2830
|
* this to toast the user; the write itself already succeeded.
|
|
1805
2831
|
*/
|
|
1806
2832
|
onWriteWarning(callback: WriteWarningListener): () => void;
|
|
@@ -1809,7 +2835,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1809
2835
|
* advisory findings on a save that SUCCEEDED (#4237; backend
|
|
1810
2836
|
* objectstack#7435). Returns an unsubscribe function.
|
|
1811
2837
|
*
|
|
1812
|
-
* Deliberately the same seam as {@link onWriteWarning} (#3431
|
|
2838
|
+
* Deliberately the same seam as {@link onWriteWarning} (objectstack-ai/objectstack#3431 / objectstack-ai/objectstack#3455), which
|
|
1813
2839
|
* is what {@link MetadataSaveAdvisoryEvent}'s own declaration already said it
|
|
1814
2840
|
* was modelled on. It is a SIBLING of that channel rather than a second
|
|
1815
2841
|
* payload pushed down it: `WriteWarningEvent` is a closed shape whose
|
|
@@ -1833,6 +2859,32 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1833
2859
|
* nor starve the others.
|
|
1834
2860
|
*/
|
|
1835
2861
|
private emitSaveAdvisory;
|
|
2862
|
+
/**
|
|
2863
|
+
* Subscribe to metadata read-warning events — a metadata READ that could not
|
|
2864
|
+
* be answered and was degraded to an empty result anyway (objectui#7741).
|
|
2865
|
+
* Returns an unsubscribe function. The app shell uses this to toast the user;
|
|
2866
|
+
* the caller has already been handed the empty result.
|
|
2867
|
+
*
|
|
2868
|
+
* Deliberately the same seam as {@link onWriteWarning} and
|
|
2869
|
+
* {@link onSaveAdvisory}, and a SIBLING of them rather than a payload pushed
|
|
2870
|
+
* down either: both of those announce a write that SUCCEEDED, and an event
|
|
2871
|
+
* saying "this read did not happen" carried on one of them would make it lie
|
|
2872
|
+
* about what occurred. What is reused is the seam's SHAPE — one long-lived
|
|
2873
|
+
* instance with a `subscribe -> unsubscribe` registration that
|
|
2874
|
+
* `AdapterProvider` wires once — not its event type.
|
|
2875
|
+
*
|
|
2876
|
+
* ⛔ Nothing here changes what a caller RECEIVES. `listImportMappings` still
|
|
2877
|
+
* answers `[]` on every failure, including the loud ones; this channel is
|
|
2878
|
+
* added ALONGSIDE that return, which is why it is not a change to a surface
|
|
2879
|
+
* published since `@object-ui/data-objectstack@17.1.0`.
|
|
2880
|
+
*/
|
|
2881
|
+
onMetadataReadWarning(callback: MetadataReadWarningListener): () => void;
|
|
2882
|
+
/**
|
|
2883
|
+
* Notify all metadata read-warning subscribers. Isolated exactly like
|
|
2884
|
+
* {@link emitWriteWarning}: a throwing listener must neither break the caller
|
|
2885
|
+
* nor starve the others.
|
|
2886
|
+
*/
|
|
2887
|
+
private emitMetadataReadWarning;
|
|
1836
2888
|
/**
|
|
1837
2889
|
* Install the ONE emitter for the metadata save door (#4237).
|
|
1838
2890
|
*
|
|
@@ -1939,25 +2991,25 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
1939
2991
|
* @returns Promise resolving to array of results
|
|
1940
2992
|
*/
|
|
1941
2993
|
/**
|
|
1942
|
-
* Cross-object transactional batch (
|
|
2994
|
+
* Cross-object transactional batch (objectstack-ai/objectstack#1604 / ADR-0034 item 4).
|
|
1943
2995
|
* Runs the operations in ONE server transaction — commit all or roll back
|
|
1944
2996
|
* all. A field value of `{ $ref: <earlier op index> }` resolves to that op's
|
|
1945
2997
|
* created id, so a child can reference its parent created earlier in the same
|
|
1946
2998
|
* batch (master-detail).
|
|
1947
2999
|
*
|
|
1948
3000
|
* Transport: the published `@objectstack/client` SDK method
|
|
1949
|
-
* `data.batchTransaction` (
|
|
3001
|
+
* `data.batchTransaction` (objectstack-ai/objectstack#3271; shipped since client v16, our
|
|
1950
3002
|
* dependency floor). Per AGENTS.md §7 data always flows through the client —
|
|
1951
3003
|
* never a hand-rolled `fetch('/api/v1/batch')`.
|
|
1952
3004
|
*
|
|
1953
|
-
* Fallback decision — declarative capability negotiation (
|
|
3005
|
+
* Fallback decision — declarative capability negotiation (objectstack-ai/objectstack#3298 /
|
|
1954
3006
|
* objectui #2693). At connect() we read `capabilities.transactionalBatch`
|
|
1955
3007
|
* from discovery:
|
|
1956
3008
|
* - Declared `true` → the backend GUARANTEES atomicity (declared ===
|
|
1957
3009
|
* enforced). We TRUST it: any batch failure — including 404/405/501 —
|
|
1958
3010
|
* surfaces as a real error. No non-atomic client-side compensation. This
|
|
1959
3011
|
* is the path modern backends take.
|
|
1960
|
-
* - Declared `false`, or ABSENT (backend predates #3298) → we can't rely on
|
|
3012
|
+
* - Declared `false`, or ABSENT (backend predates objectstack-ai/objectstack#3298) → we can't rely on
|
|
1961
3013
|
* server atomicity, so we keep the legacy behaviour: on 404/405 (no
|
|
1962
3014
|
* endpoint) or 501 (runtime without transactions) degrade to the
|
|
1963
3015
|
* client-side, NON-atomic {@link emulateBatchTransaction} so a save is
|
|
@@ -2050,6 +3102,30 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2050
3102
|
* the caller triggers the browser download.
|
|
2051
3103
|
*/
|
|
2052
3104
|
exportDownload(resource: string, request?: ExportDownloadRequest): Promise<Blob>;
|
|
3105
|
+
/**
|
|
3106
|
+
* Download the server's import template:
|
|
3107
|
+
* `GET /api/v1/data/:object/export?template=true` (objectui#9600).
|
|
3108
|
+
*
|
|
3109
|
+
* Same route as {@link exportDownload}, with the server's template switch. It
|
|
3110
|
+
* sends only `template=true`: the server refuses the export's row parameters
|
|
3111
|
+
* on a template request, and its answer is always an xlsx workbook.
|
|
3112
|
+
*
|
|
3113
|
+
* The server writes the template's labels in the request's locale, and the
|
|
3114
|
+
* import accepts a translated label only in its own request's locale. The
|
|
3115
|
+
* import request goes through `@objectstack/client`, which stamps
|
|
3116
|
+
* `Accept-Language` with the client's locale when one is set (and the host's
|
|
3117
|
+
* `fetch` stamps its own otherwise). This request bypasses the client, so it
|
|
3118
|
+
* stamps the client's locale the same way. Both requests then carry the same
|
|
3119
|
+
* locale.
|
|
3120
|
+
*/
|
|
3121
|
+
downloadImportTemplate(resource: string): Promise<Blob>;
|
|
3122
|
+
/**
|
|
3123
|
+
* GET `/api/v1/data/:object/export` with the given query and answer the body
|
|
3124
|
+
* as a Blob. A failure throws an `Error` that carries the server's `status`
|
|
3125
|
+
* and its ADR-0112 `code`. The one request path {@link exportDownload} and
|
|
3126
|
+
* {@link downloadImportTemplate} share.
|
|
3127
|
+
*/
|
|
3128
|
+
private fetchExportBlob;
|
|
2053
3129
|
/**
|
|
2054
3130
|
* Convert ObjectUI QueryParams to ObjectStack QueryOptions.
|
|
2055
3131
|
* Maps OData-style conventions to ObjectStack conventions.
|
|
@@ -2213,23 +3289,81 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2213
3289
|
*/
|
|
2214
3290
|
getView(objectName: string, viewId: string): Promise<unknown | null>;
|
|
2215
3291
|
/**
|
|
2216
|
-
* Persist a view
|
|
2217
|
-
*
|
|
2218
|
-
*
|
|
2219
|
-
*
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2222
|
-
*
|
|
2223
|
-
*
|
|
2224
|
-
*
|
|
2225
|
-
*
|
|
2226
|
-
*
|
|
3292
|
+
* Persist a toolbar-driven view config patch — density, column widths,
|
|
3293
|
+
* sort, hidden columns, inline edit. Symmetric counterpart to
|
|
3294
|
+
* {@link getView}: writes the row to the server metadata store via
|
|
3295
|
+
* `client.meta.saveItem`, then invalidates the matching cache entry so the
|
|
3296
|
+
* next {@link getView} reflects the new payload. Returns the persisted item
|
|
3297
|
+
* when the server echoes it, otherwise undefined.
|
|
3298
|
+
*
|
|
3299
|
+
* Called from exactly ONE production site — `ObjectView`'s
|
|
3300
|
+
* `persistViewPatch`, for the toolbar toggle — but that ONE call site
|
|
3301
|
+
* fires for BOTH kinds of active tab: a code-defined **system** view (no
|
|
3302
|
+
* row of its own yet) and a genuinely user-created **saved** view (already
|
|
3303
|
+
* has a row — the toggle is editing ITS OWN definition, not laying an
|
|
3304
|
+
* overlay on top of it). Which one a given call means is NOT re-derived
|
|
3305
|
+
* here from the write's shape (objectui#4227's own lesson: shape inference
|
|
3306
|
+
* on this namespace is exactly what let a system view masquerade as
|
|
3307
|
+
* saved) — the caller already knows, via the same `isSavedViewId`
|
|
3308
|
+
* classification that gates the switcher's readonly flag and its five
|
|
3309
|
+
* mutating handlers, and passes it as {@link opts.isSavedView}.
|
|
3310
|
+
*
|
|
3311
|
+
* - `isSavedView` false/omitted (system-view target, the common case and
|
|
3312
|
+
* the default for backward compatibility): stamps
|
|
3313
|
+
* {@link VIEW_OVERLAY_MARKER} so {@link listViews} excludes the row —
|
|
3314
|
+
* the original objectui#4227 fix.
|
|
3315
|
+
* - `isSavedView` true: the marker is withheld. Stamping it here would
|
|
3316
|
+
* flag the saved view's OWN row as a personalization overlay, and
|
|
3317
|
+
* `listViews()` would exclude it on the very next read — the user's own
|
|
3318
|
+
* view would vanish from the switcher the moment they toggled its
|
|
3319
|
+
* density (objectui#4227 follow-up, PM review on PR #4713, measured:
|
|
3320
|
+
* `persistViewPatch` has no gate on which kind of tab is active, and
|
|
3321
|
+
* this method writes to the exact same `(type='view', name=viewId)` key
|
|
3322
|
+
* {@link createView}/the ADR-0034 `viewEnvelope` seam already used for
|
|
3323
|
+
* that view, so the write is an upsert onto the saved view's row, not a
|
|
3324
|
+
* new one).
|
|
3325
|
+
*
|
|
3326
|
+
* Per objectstack#7494's ruling, the overlay this writes (system-view
|
|
3327
|
+
* case) is ORG-WIDE shared view settings, not a per-user preference — a
|
|
3328
|
+
* true per-user scope is a parked v18 direction on the platform side.
|
|
2227
3329
|
*
|
|
2228
3330
|
* @param objectName - Object name (e.g. 'lead')
|
|
2229
3331
|
* @param viewId - View identifier (e.g. 'all_leads')
|
|
2230
3332
|
* @param config - Full view definition to persist
|
|
3333
|
+
* @param opts.isSavedView - Whether `viewId` already names a saved view
|
|
3334
|
+
* (vs. a system view being personalized for the first time). Omit /
|
|
3335
|
+
* `false` for the default overlay-marking behavior.
|
|
3336
|
+
*/
|
|
3337
|
+
/**
|
|
3338
|
+
* [ADR-0066] Tell this adapter which system capabilities the session holds.
|
|
3339
|
+
*
|
|
3340
|
+
* Pass `undefined` for "no answer was reported" (a backend predating
|
|
3341
|
+
* ADR-0066, or no permission provider mounted). Pass `[]` for a real,
|
|
3342
|
+
* reported, empty grant — {@link maySetViewConfig} treats those two
|
|
3343
|
+
* differently and callers must not collapse them, which is the same
|
|
3344
|
+
* distinction `MePermissionsProvider` preserves natively (objectui#4656).
|
|
2231
3345
|
*/
|
|
2232
|
-
|
|
3346
|
+
setSystemCapabilities(capabilities: string[] | undefined): void;
|
|
3347
|
+
/**
|
|
3348
|
+
* May this session write ORG-WIDE view configuration?
|
|
3349
|
+
*
|
|
3350
|
+
* **Unknown fails OPEN**, deliberately, and this is the one judgement in the
|
|
3351
|
+
* gate worth reading twice. It is the doctrine `PermissionContextValue`
|
|
3352
|
+
* .`hasCapabilities` states for every ADR-0066 gate in this repo
|
|
3353
|
+
* (objectui#4656, framework#3923): the server enforces `manage_metadata` on
|
|
3354
|
+
* the metadata door regardless of what the client believes, so a client-side
|
|
3355
|
+
* denial on MISSING DATA cannot protect anything the server was not already
|
|
3356
|
+
* protecting — it can only break a permitted user. Failing closed here would
|
|
3357
|
+
* have refused the write on every deployment predating ADR-0066 and every
|
|
3358
|
+
* host with no permission provider, which is "break the write for everyone"
|
|
3359
|
+
* wearing the costume of a security fix.
|
|
3360
|
+
*
|
|
3361
|
+
* A REPORTED empty array gates strictly: "holds nothing" is a real answer.
|
|
3362
|
+
*/
|
|
3363
|
+
private maySetViewConfig;
|
|
3364
|
+
updateViewConfig(objectName: string, viewId: string, config: Record<string, any>, opts?: {
|
|
3365
|
+
isSavedView?: boolean;
|
|
3366
|
+
}): Promise<Record<string, any> | void>;
|
|
2233
3367
|
/**
|
|
2234
3368
|
* List user-created views for a given object via the metadata overlay
|
|
2235
3369
|
* API (ADR-0005). Replaces the legacy `find('sys_view', {...})` path
|
|
@@ -2241,6 +3375,42 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2241
3375
|
* the metadata index is name-only, not field-typed, so the route has no
|
|
2242
3376
|
* `?object=` to push the filter down into. {@link listViewOverrides}
|
|
2243
3377
|
* reads the same rows through the same accessor.
|
|
3378
|
+
*
|
|
3379
|
+
* ## Every failure still degrades to an empty list — and now says which kind
|
|
3380
|
+
* ## of failure it was (objectui#8151)
|
|
3381
|
+
*
|
|
3382
|
+
* The degrade is unchanged and deliberate, exactly as it is one method over
|
|
3383
|
+
* in {@link listImportMappings}: a host with no metadata door keeps answering
|
|
3384
|
+
* `[]` rather than breaking the page. What changed is that the OTHER failures
|
|
3385
|
+
* no longer render as that one.
|
|
3386
|
+
*
|
|
3387
|
+
* This method's empty list is read as *this object has no saved views*, and
|
|
3388
|
+
* that reading reaches the user in two places measured on this tree: the list
|
|
3389
|
+
* view switcher simply shows fewer tabs, and `@object-ui/core`'s
|
|
3390
|
+
* `elementDataSourceViewNotFoundMessage` states it outright. So a session
|
|
3391
|
+
* whose token lapsed mid-read was shown an object that appears to have no
|
|
3392
|
+
* saved views AT ALL — including ones the user created — with a
|
|
3393
|
+
* `console.warn` as the only discriminator, in a console nothing in the UI
|
|
3394
|
+
* points at. That is the silence objectui#7741 removed from the sibling after
|
|
3395
|
+
* it produced a confident wrong diagnosis in objectstack#14026.
|
|
3396
|
+
*
|
|
3397
|
+
* The `catch` now asks {@link classifyViewsFailure} what the failure WAS,
|
|
3398
|
+
* reading the error's own ADR-0112 `code` and status — never "is the result
|
|
3399
|
+
* empty", which is what both conditions produce and so can never tell them
|
|
3400
|
+
* apart — and anything that is not the doorless shape is announced on
|
|
3401
|
+
* {@link onMetadataReadWarning}.
|
|
3402
|
+
*
|
|
3403
|
+
* ⛔ That classifier is a SEPARATE reading, not a second caller of the
|
|
3404
|
+
* sibling's: `view`'s quiet set is strictly smaller, because the arm
|
|
3405
|
+
* `listImportMappings` is built around (*an older server without this kind*)
|
|
3406
|
+
* has no members here. See {@link classifyViewsFailure} for the measurement.
|
|
3407
|
+
*
|
|
3408
|
+
* ⛔ The RETURN is untouched. This method has answered `Promise<any[]>`, never
|
|
3409
|
+
* throwing, since `@object-ui/data-objectstack@17.1.0`; a consumer that reads
|
|
3410
|
+
* nothing new sees exactly what it saw before, including on the loud arms.
|
|
3411
|
+
* Applying framework objectstack#13906 decision 1 option A — *a thing that could not be
|
|
3412
|
+
* READ is not a thing that is ABSENT* — is done by ADDING a channel, not by
|
|
3413
|
+
* moving that contract.
|
|
2244
3414
|
*/
|
|
2245
3415
|
listViews(objectName: string, options?: {
|
|
2246
3416
|
previewDrafts?: boolean;
|
|
@@ -2254,11 +3424,36 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2254
3424
|
private metadataClient;
|
|
2255
3425
|
/**
|
|
2256
3426
|
* List registered import `mapping` artifacts targeting a given object
|
|
2257
|
-
* (
|
|
3427
|
+
* (objectstack-ai/objectstack#2611). Reads the `mapping` metadata kind via the overlay API
|
|
2258
3428
|
* and filters by `targetObject` client-side (the metadata index is
|
|
2259
|
-
* name-only). Feeds the import wizard's "saved mapping" selector
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
3429
|
+
* name-only). Feeds the import wizard's "saved mapping" selector.
|
|
3430
|
+
*
|
|
3431
|
+
* ## Every failure still degrades to an empty list — and now says which kind
|
|
3432
|
+
* ## of failure it was (objectui#7741)
|
|
3433
|
+
*
|
|
3434
|
+
* The degrade is unchanged and deliberate: an older server without the
|
|
3435
|
+
* `mapping` kind must not break the wizard, so it keeps answering `[]` and
|
|
3436
|
+
* the selector simply doesn't appear. That is a real, supported deployment
|
|
3437
|
+
* shape and it stays quiet.
|
|
3438
|
+
*
|
|
3439
|
+
* What changed is that the OTHER failures no longer render as that one. A
|
|
3440
|
+
* refused door and a broken one used to be indistinguishable, in the UI, from
|
|
3441
|
+
* "no mapping is registered" — both were an empty list behind a
|
|
3442
|
+
* `console.warn` nothing pointed at — and that silence produced a confident
|
|
3443
|
+
* wrong diagnosis in a careful reporter (objectstack#14026 was filed, routed
|
|
3444
|
+
* and worked by two seats against a wizard that had been correct since
|
|
3445
|
+
* 17.1.0). So the `catch` now asks {@link classifyImportMappingsFailure} what
|
|
3446
|
+
* the failure WAS, reading the error's own ADR-0112 `code` and status —
|
|
3447
|
+
* never "is the result empty", which is what both conditions produce and so
|
|
3448
|
+
* can never tell them apart — and anything that is not the supported
|
|
3449
|
+
* kind-absent shape is announced on {@link onMetadataReadWarning}.
|
|
3450
|
+
*
|
|
3451
|
+
* ⛔ The RETURN is untouched. This method has answered `Promise<any[]>`, never
|
|
3452
|
+
* throwing, since `@object-ui/data-objectstack@17.1.0`; a consumer that reads
|
|
3453
|
+
* nothing new sees exactly what it saw before, including on the loud arms.
|
|
3454
|
+
* Applying framework objectstack#13906 decision 1 option A — *a thing that could not be
|
|
3455
|
+
* READ is not a thing that is ABSENT* — is done by ADDING a channel, not by
|
|
3456
|
+
* moving that contract.
|
|
2262
3457
|
*/
|
|
2263
3458
|
listImportMappings(objectName: string): Promise<any[]>;
|
|
2264
3459
|
/**
|
|
@@ -2403,8 +3598,13 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2403
3598
|
* Ask the by-name meta app route WHY an app is not in this session's app list
|
|
2404
3599
|
* (objectui#4252).
|
|
2405
3600
|
*
|
|
2406
|
-
* `GET /api/v1/meta
|
|
2407
|
-
*
|
|
3601
|
+
* The app LIST is the generic metadata list route `GET /api/v1/meta/:type`,
|
|
3602
|
+
* requested with the singular type segment `app` — the same address this
|
|
3603
|
+
* method appends a name to below, and the one `MetadataProvider` reads its
|
|
3604
|
+
* items from. The server filters that list per session in `filterAppForUser`
|
|
3605
|
+
* (`packages/rest/src/rest-server.ts`, applied inside the `:type` list handler
|
|
3606
|
+
* once the type segment resolves to `app`), so an app withheld by
|
|
3607
|
+
* `requiredPermissions` and an app
|
|
2408
3608
|
* that does not exist are byte-identical there: both are simply absent. A
|
|
2409
3609
|
* console reading only that list has one fact and two conditions, and it
|
|
2410
3610
|
* renders its copy for the wrong one — "it may still be publishing" over a
|
|
@@ -2414,11 +3614,17 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2414
3614
|
*
|
|
2415
3615
|
* The maintainer ruling (2026-08-12) put the answer on the BY-NAME route
|
|
2416
3616
|
* rather than in the list, so the enumeration surface is not widened past what
|
|
2417
|
-
* a by-name probe already implies (objectstack#8013 / PR #8135): an app that
|
|
3617
|
+
* a by-name probe already implies (objectstack#8013 / PR objectstack-ai/objectstack#8135): an app that
|
|
2418
3618
|
* exists and whose `requiredPermissions` the session lacks answers `403` with
|
|
2419
3619
|
* `PERMISSION_DENIED` in the declared envelope, and absence — a nonexistent
|
|
2420
|
-
* name, an unpublished app, an app gated by an absent optional service —
|
|
2421
|
-
*
|
|
3620
|
+
* name, an unpublished app, an app gated by an absent optional service — is
|
|
3621
|
+
* one undifferentiated answer.
|
|
3622
|
+
*
|
|
3623
|
+
* ⚠️ That absence is NOT uniformly a `404`, which this docstring asserted
|
|
3624
|
+
* until objectui#9262 measured it: the 404 covers an app that EXISTS and is
|
|
3625
|
+
* withheld for a non-permission reason, while a name that resolves to nothing
|
|
3626
|
+
* answers `200` with an envelope carrying no `item`. Both are absence and both
|
|
3627
|
+
* read as `not_found`; see {@link metaEnvelopeCarriesItem} for the measurement.
|
|
2422
3628
|
*
|
|
2423
3629
|
* ## Why this is a separate method and not a flavour of {@link getApp}
|
|
2424
3630
|
*
|
|
@@ -2429,9 +3635,29 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2429
3635
|
* be cached beside a document about the APP — one grant, and a cached
|
|
2430
3636
|
* denial outlives the session it described.
|
|
2431
3637
|
*
|
|
2432
|
-
* Nothing here throws:
|
|
2433
|
-
*
|
|
2434
|
-
*
|
|
3638
|
+
* Nothing here throws: every outcome is a verdict the caller renders a screen
|
|
3639
|
+
* from, never a catch block. Only the measured `code` produces `denied` —
|
|
3640
|
+
* never a status, never a message (objectui#4408).
|
|
3641
|
+
*
|
|
3642
|
+
* ## objectui#9262 — what this method stopped folding away
|
|
3643
|
+
*
|
|
3644
|
+
* It used to answer `granted` whenever the call did not throw, and `unknown`
|
|
3645
|
+
* for everything that did. Both halves were wrong in the same direction, and
|
|
3646
|
+
* the first was measured on a real server before this change landed:
|
|
3647
|
+
*
|
|
3648
|
+
* - **an app that does not exist answers `200` with the envelope minus its
|
|
3649
|
+
* `item`**, so "did not throw" reported `granted` for a name with nothing
|
|
3650
|
+
* behind it — the shape issue objectui#9262 listed as cause 8 and expected
|
|
3651
|
+
* to be structural. It is the ordinary case: every typo, every never-created
|
|
3652
|
+
* app and every unpublished draft took it. See
|
|
3653
|
+
* {@link metaEnvelopeCarriesItem} for the measurement and the server-side
|
|
3654
|
+
* mechanism.
|
|
3655
|
+
* - a 404 absence and an unreachable server were one `unknown`, though the
|
|
3656
|
+
* transport had already told them apart.
|
|
3657
|
+
*
|
|
3658
|
+
* The verdicts now say only what was obtained: a served document, a refusal
|
|
3659
|
+
* naming permission, an answer that there is no such app here, or no answer
|
|
3660
|
+
* at all. `unknown` is left for the one case where nothing was asked.
|
|
2435
3661
|
*
|
|
2436
3662
|
* @param appName - the app name as it appears in the URL segment
|
|
2437
3663
|
*/
|
|
@@ -2460,9 +3686,28 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2460
3686
|
* Uses `this.client.analytics.query()` from @objectstack/client to leverage
|
|
2461
3687
|
* the SDK's built-in auth, headers, and fetch configuration.
|
|
2462
3688
|
* Falls back to client-side aggregation via find() if the analytics endpoint
|
|
2463
|
-
* is not available.
|
|
3689
|
+
* is not available. Throws {@link AnalyticsResultShapeError} when it answers
|
|
3690
|
+
* with anything but an `AnalyticsResult` carrying `rows`.
|
|
2464
3691
|
*/
|
|
2465
3692
|
aggregate(resource: string, params: any): Promise<any[]>;
|
|
3693
|
+
/**
|
|
3694
|
+
* Answer a grouped list view's GROUP HEADER query (objectui#7189) — see
|
|
3695
|
+
* `DataSource.queryGroupHeaders` in `@object-ui/types` for the contract.
|
|
3696
|
+
*
|
|
3697
|
+
* The compiled query rides the platform's EXISTING data door verbatim:
|
|
3698
|
+
* `client.data.query()` posts it to `POST /data/:object/query`, whose
|
|
3699
|
+
* `findData` routes a body carrying `groupBy` / `aggregations` to
|
|
3700
|
+
* `engine.aggregate` and answers the header rows as `records` (the door the
|
|
3701
|
+
* spec half of objectstack#14556 names, pinned platform-side by
|
|
3702
|
+
* objectstack#15330). No new route, no new wire shape.
|
|
3703
|
+
*
|
|
3704
|
+
* ⛔ No fallback, deliberately — unlike {@link aggregate}. A header query that
|
|
3705
|
+
* degraded to bucketing a fetched page would answer page slices under a name
|
|
3706
|
+
* that promises the query's own numbers, which is the defect this member
|
|
3707
|
+
* exists to end. A failure is thrown to the caller, and a body without a
|
|
3708
|
+
* `records` array is refused rather than read as "no groups".
|
|
3709
|
+
*/
|
|
3710
|
+
queryGroupHeaders(resource: string, query: EngineAggregateOptions): Promise<ListViewGroupHeaderRow[]>;
|
|
2466
3711
|
/**
|
|
2467
3712
|
* Client-side aggregation over a server-scoped `find()` — the fallback used
|
|
2468
3713
|
* whenever the analytics endpoint cannot answer (capability absent, network
|
|
@@ -2648,6 +3893,21 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2648
3893
|
/**
|
|
2649
3894
|
* Factory function to create an ObjectStack data source.
|
|
2650
3895
|
*
|
|
3896
|
+
* The declared return is `ObjectStackAdapter<T>`, not the shared
|
|
3897
|
+
* `DataSource<T>` interface: the object this hands back is an
|
|
3898
|
+
* `ObjectStackAdapter`, and the adapter-only members the package README
|
|
3899
|
+
* documents — `getClient`, the metadata-cache controls, the connection-state
|
|
3900
|
+
* and batch-progress subscriptions, and `setSystemCapabilities` — live on the
|
|
3901
|
+
* class, not on `DataSource`. Declaring the interface here narrowed all of them
|
|
3902
|
+
* away from the factory while leaving them on the value (objectui#7323), so
|
|
3903
|
+
* `createObjectStackAdapter(...)` and `new ObjectStackAdapter(...)` — the two
|
|
3904
|
+
* documented ways to obtain the same object — handed back different type
|
|
3905
|
+
* surfaces. ⛔ Do not narrow this back to `DataSource<T>`: a caller who wants
|
|
3906
|
+
* the narrow surface writes `const ds: DataSource = createObjectStackAdapter(…)`
|
|
3907
|
+
* and gets it, because a wider return is assignable to the narrower annotation;
|
|
3908
|
+
* the reverse is not recoverable at the call site without a cast.
|
|
3909
|
+
* `adapterFactoryReturn.types.test.ts` pins both directions.
|
|
3910
|
+
*
|
|
2651
3911
|
* @example
|
|
2652
3912
|
* ```typescript
|
|
2653
3913
|
* const dataSource = createObjectStackAdapter({
|
|
@@ -2670,6 +3930,8 @@ declare function createObjectStackAdapter<T = unknown>(config: {
|
|
|
2670
3930
|
autoReconnect?: boolean;
|
|
2671
3931
|
maxReconnectAttempts?: number;
|
|
2672
3932
|
reconnectDelay?: number;
|
|
2673
|
-
}
|
|
3933
|
+
/** [ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}. */
|
|
3934
|
+
systemCapabilities?: string[];
|
|
3935
|
+
}): ObjectStackAdapter<T>;
|
|
2674
3936
|
|
|
2675
|
-
export { API_ACCESS_DENIED_CODES, APP_PERMISSION_DENIED_CODE,
|
|
3937
|
+
export { API_ACCESS_DENIED_CODES, APP_PERMISSION_DENIED_CODE, AnalyticsDatasetNotFoundError, AnalyticsForbiddenError, AnalyticsKeysOnSpecShapeError, AnalyticsNotInstalledError, AnalyticsQueryRejectedError, AnalyticsResultShapeError, AnalyticsUnauthenticatedError, type AppAccessVerdict, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, CHOICE_TYPES_REQUIRING_OPTIONS, ConcurrentUpdateError, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, DataApiValidationError, type DroppedFieldsNotice, FILTER_OPERATOR_ALIASES, type ImportMappingsFailureKind, META_ITEM_ABSENT_CODE, 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, type MetadataLockState, MetadataNotFoundError, type MetadataOverlayScope, type MetadataReadFailureKind, type MetadataReadWarningEvent, type MetadataReadWarningListener, type MetadataReference, type MetadataSaveAdvisoryEvent, type MetadataSaveAdvisoryListener, type MetadataValidationIssue, OBJECT_METADATA_TYPE, ObjectStackAdapter, ObjectStackError, type ObjectStackUserStateAdapterOptions, RELATIONSHIP_TYPES_REQUIRING_REFERENCE, UNRECOGNIZED_DROP_REASON, UnlowerableAnalyticsFilterError, UnloweredAggregateWhereError, type UnrecognizedDropReasonEvent, type UserDataAdapter, VIEW_CONFIG_CAPABILITY, VIEW_OVERLAY_OWNED_KEYS, ViewConfigPermissionDeniedError, type WriteWarningEvent, type WriteWarningListener, assertObjectMetadataWritable, classifyAnalyticsFailure, classifyImportMappingsFailure, classifyViewsFailure, clearSharedDiscoveryCache, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, createQuietHttpLogger, extractDraftBody, formatHttpFailureMessage, formatMetadataError, formatMetadataIssue, getSharedDiscovery, is404Error, isAnalyticsNotInstalledError, isApiAccessDeniedError, isAppPermissionDeniedError, isConcurrentUpdateError, isErrorType, isMalformedFilterError, isMetaItemAbsentError, isObjectStackError, isViewConfigPermissionDeniedError, narrowPersonalizationOverlay, normaliseClientError, readSaveAdvisories, readTransactionalBatchCapability, serializeOrderBy, viewItemObjectName };
|