@object-ui/data-objectstack 17.5.0 → 17.6.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 +219 -0
- package/dist/index.cjs +262 -357
- package/dist/index.d.cts +385 -448
- package/dist/index.d.ts +385 -448
- package/dist/index.js +258 -350
- package/package.json +5 -6
- 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.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ObjectStackClient } from '@objectstack/client';
|
|
2
2
|
import { DroppedFieldsEvent } from '@objectstack/spec/data';
|
|
3
3
|
export { DroppedFieldsEvent } from '@objectstack/spec/data';
|
|
4
|
-
import { RuntimeAuthoringIssue } from '@objectstack/spec/api';
|
|
4
|
+
import { GetMetaItemLayeredResponse, RuntimeAuthoringIssue } from '@objectstack/spec/api';
|
|
5
5
|
export { RuntimeAuthoringIssue } from '@objectstack/spec/api';
|
|
6
6
|
import { DatasetSelection, AnalyticsResult } from '@objectstack/spec/contracts';
|
|
7
7
|
import { DataSource, QueryParams, QueryResult, GlobalSearchResult, DataSourceMutationEvent, BatchTransactionOperation, ImportRequestOptions, ImportRecordsResult, CreateImportJobResult, ImportJobProgressInfo, ImportJobResultsInfo, ListImportJobsOptions, ImportJobSummaryInfo, ImportJobUndoResult, ExportDownloadRequest, DeleteViewResult, FileUploadResult } from '@object-ui/types';
|
|
@@ -146,9 +146,9 @@ interface MetadataDraftHeader {
|
|
|
146
146
|
* metadata item to a FILE — `format: json|yaml|ts`, `path`, `indent`,
|
|
147
147
|
* `prettify`, `sortKeys`, `backup`, `atomic`, `loader`. Not one of those keys
|
|
148
148
|
* 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
|
-
*
|
|
149
|
+
* request envelope — optimistic concurrency (`ifMatch` → `If-Match`), the
|
|
150
|
+
* destructive-change override, the ADR-0033 draft/publish mode, and the
|
|
151
|
+
* owning package. Same words, different layer.
|
|
152
152
|
*/
|
|
153
153
|
interface MetadataClientSaveOptions {
|
|
154
154
|
/**
|
|
@@ -157,8 +157,6 @@ interface MetadataClientSaveOptions {
|
|
|
157
157
|
* edits get a 409 instead of overwriting each other.
|
|
158
158
|
*/
|
|
159
159
|
ifMatch?: string;
|
|
160
|
-
/** Optional actor id, sent as `X-Actor` for history attribution. */
|
|
161
|
-
actor?: string;
|
|
162
160
|
/**
|
|
163
161
|
* Bypass destructive-change protection (Phase 3a). The server returns
|
|
164
162
|
* `409 destructive_change` with `issues[]` if a write would drop or
|
|
@@ -203,14 +201,74 @@ interface MetadataDeleteOptions extends MetadataClientSaveOptions {
|
|
|
203
201
|
*/
|
|
204
202
|
state?: 'active' | 'draft';
|
|
205
203
|
}
|
|
206
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* Which layer an overlay was saved at — `'org'` for a tenant overlay, `'env'`
|
|
206
|
+
* for an environment-level one, `null` exactly when `overlay` is null.
|
|
207
|
+
*
|
|
208
|
+
* DERIVED from `@objectstack/spec`, not restated: the vocabulary lives once, in
|
|
209
|
+
* `GetMetaItemLayeredResponseSchema`'s `overlayScope`
|
|
210
|
+
* (`z.enum(['org', 'env']).nullable()`), and this alias indexes the published
|
|
211
|
+
* response type so a scope the spec adds arrives here with no edit. Restating
|
|
212
|
+
* the union locally is the fork `scripts/check-spec-symbol-derivation.mjs`
|
|
213
|
+
* exists to reject, and the mirror-image mistake — a consumer re-spelling a
|
|
214
|
+
* producer's enum — is what this field carried until objectui#4982.
|
|
215
|
+
*
|
|
216
|
+
* What it carried: `string | null`, under a comment naming the vocabulary as
|
|
217
|
+
* `organization | environment | package`. Not one of those three spellings is a
|
|
218
|
+
* value the producer emits (`metadata-protocol`'s two assignment sites write
|
|
219
|
+
* `'org'` / `'env'`); the schema rejects all three by name, and `package` is
|
|
220
|
+
* not a scope this field has ever had. Because the declared type was `string`
|
|
221
|
+
* the compiler had no opinion, so the wrong comment was the only description of
|
|
222
|
+
* the vocabulary — a planted premise rather than stale prose, and the reason
|
|
223
|
+
* the Studio's layer badge shipped the raw value straight to screen.
|
|
224
|
+
*/
|
|
225
|
+
type MetadataOverlayScope = GetMetaItemLayeredResponse['overlayScope'];
|
|
226
|
+
/**
|
|
227
|
+
* ADR-0010 §3.6 — the four-state metadata protection lock
|
|
228
|
+
* (`none` / `no-overlay` / `no-delete` / `full`), read from
|
|
229
|
+
* `GetMetaItemLayeredResponseSchema`'s own `z.enum` rather than spelled out
|
|
230
|
+
* again here (objectui#5024).
|
|
231
|
+
*
|
|
232
|
+
* It used to be written out twice in this file, 42 lines apart: once as
|
|
233
|
+
* {@link MetadataLayered.lock} (optional) and once as
|
|
234
|
+
* {@link MetadataAuditEntry.lockState} (nullable), identical in every other
|
|
235
|
+
* respect and compared by no gate. A fifth state added to one would have left
|
|
236
|
+
* the other compiling — the same "declared N times, diffed by nothing" failure
|
|
237
|
+
* objectui#4972 and objectui#4984 record for other vocabularies.
|
|
238
|
+
*
|
|
239
|
+
* Deriving beats a local alias the two merely share. The producer of these
|
|
240
|
+
* values is the framework, and `packages/spec` already declares the vocabulary,
|
|
241
|
+
* so the copies were restating a schema that existed rather than filling a gap.
|
|
242
|
+
* The card that reported this recorded the opposite — "`@objectstack/spec` 也没
|
|
243
|
+
* 有对应的 `z.enum` 可派生" — and so did the audit panel's neighbouring comment;
|
|
244
|
+
* both predate the enum, which ships in `@objectstack/spec` 17.1.0. This is the
|
|
245
|
+
* same treatment {@link MetadataOverlayScope} above already gets, and it closes
|
|
246
|
+
* the cross-repo half of the drift, not just the in-repo half.
|
|
247
|
+
*
|
|
248
|
+
* ⚠️ This types what this repo may WRITE. It does NOT constrain what a server
|
|
249
|
+
* may SEND: {@link MetadataClient.layered} casts the wire value through
|
|
250
|
+
* unchecked, so every reader must still handle a value outside these four. The
|
|
251
|
+
* lock banner in `ResourceEditPage` is the worked example.
|
|
252
|
+
*/
|
|
253
|
+
type MetadataLockState = GetMetaItemLayeredResponse['lock'];
|
|
254
|
+
/**
|
|
255
|
+
* Layered view of a metadata item — the body of
|
|
256
|
+
* `GET /meta/:type/:name/layers` (`GetMetaItemLayeredResponseSchema`).
|
|
257
|
+
*
|
|
258
|
+
* A subset by design: the response also carries `type` / `name`, which the
|
|
259
|
+
* caller already knows because it passed them in, so
|
|
260
|
+
* {@link MetadataClient.layered} does not hand them back.
|
|
261
|
+
*/
|
|
207
262
|
interface MetadataLayered<T = unknown> {
|
|
208
263
|
/** Code-level (artifact) item; null if the item only exists as an overlay. */
|
|
209
264
|
code: T | null;
|
|
210
265
|
/** Org/environment overlay (just the saved delta or full overlay row). */
|
|
211
266
|
overlay: T | null;
|
|
212
|
-
/**
|
|
213
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Which layer {@link MetadataLayered.overlay} came from, or null when there
|
|
269
|
+
* is no overlay. Spec-derived — see {@link MetadataOverlayScope}.
|
|
270
|
+
*/
|
|
271
|
+
overlayScope: MetadataOverlayScope;
|
|
214
272
|
/** Merged effective view — what the runtime actually sees. */
|
|
215
273
|
effective: T | null;
|
|
216
274
|
/**
|
|
@@ -222,7 +280,7 @@ interface MetadataLayered<T = unknown> {
|
|
|
222
280
|
*/
|
|
223
281
|
_diagnostics?: MetadataDiagnostics;
|
|
224
282
|
/** 4-state lock: `none` / `no-overlay` / `no-delete` / `full`. */
|
|
225
|
-
lock?:
|
|
283
|
+
lock?: MetadataLockState;
|
|
226
284
|
/** Human-readable reason for the lock (tooltip text). */
|
|
227
285
|
lockReason?: string;
|
|
228
286
|
/** Which layer set the lock: artifact / package / overlay / env-forced. */
|
|
@@ -252,7 +310,10 @@ interface MetadataAuditEntry {
|
|
|
252
310
|
id: unknown;
|
|
253
311
|
/** ISO timestamp when the attempt happened. */
|
|
254
312
|
occurredAt: string;
|
|
255
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* Who attempted the operation — the identity the request was authorized
|
|
315
|
+
* as, or `'system'` for internal machine writes (objectstack#7941).
|
|
316
|
+
*/
|
|
256
317
|
actor: string;
|
|
257
318
|
/** Code path that recorded the row (e.g. `protocol.saveMetaItem`). */
|
|
258
319
|
source: string | null;
|
|
@@ -263,7 +324,7 @@ interface MetadataAuditEntry {
|
|
|
263
324
|
/** Machine-readable reason code (`item_locked`, `ok`, …). */
|
|
264
325
|
code: string;
|
|
265
326
|
/** Effective lock at the moment of the attempt. */
|
|
266
|
-
lockState:
|
|
327
|
+
lockState: MetadataLockState | null;
|
|
267
328
|
/** True when admin forced the write through despite the lock. */
|
|
268
329
|
lockOverridden: boolean;
|
|
269
330
|
/** Request-id for trace correlation (if propagated). */
|
|
@@ -492,9 +553,35 @@ declare class MetadataClient {
|
|
|
492
553
|
};
|
|
493
554
|
} & Record<string, unknown>>;
|
|
494
555
|
/**
|
|
495
|
-
* Get the 3-state layered view of a metadata item (
|
|
496
|
-
*
|
|
497
|
-
*
|
|
556
|
+
* Get the 3-state layered view of a metadata item: `code` (the packaged
|
|
557
|
+
* artifact baseline), `overlay` (the tenant customisation row alone) and
|
|
558
|
+
* `effective` (the merged value the runtime sees).
|
|
559
|
+
*
|
|
560
|
+
* Reads **`GET /meta/:type/:name/layers`** — the path the framework declares
|
|
561
|
+
* for this projection, with a response schema of its own
|
|
562
|
+
* (`GetMetaItemLayeredResponseSchema`, objectstack#5882 ruling B). It used to
|
|
563
|
+
* be reached by hanging a `layers` flag on the ordinary item read, which made
|
|
564
|
+
* one route answer two unrelated representations while `packages/spec`
|
|
565
|
+
* declared only one of them. That spelling still answers this same body
|
|
566
|
+
* inside its deprecation window (the response carries RFC 9745
|
|
567
|
+
* `Deprecation: true` and an RFC 8288 `Link: rel="successor-version"` back to
|
|
568
|
+
* this path) and is scheduled for removal upstream, so nothing here may
|
|
569
|
+
* depend on it — the repo-wide ratchet is
|
|
570
|
+
* `scripts/__tests__/layered-read-declared-path-4016.test.ts`.
|
|
571
|
+
*
|
|
572
|
+
* The request is built here rather than delegated to `@objectstack/client`
|
|
573
|
+
* because the SDK expresses no layered read in EITHER spelling: the
|
|
574
|
+
* framework's REST route ledger records this route as `server-only`,
|
|
575
|
+
* "consumed by objectui over plain HTTP", and whether the SDK should express
|
|
576
|
+
* it is an open upstream product call.
|
|
577
|
+
*
|
|
578
|
+
* One behaviour delta rides along with the path, and it is the server's
|
|
579
|
+
* choice rather than ours: the retired flag FELL THROUGH to the plain item
|
|
580
|
+
* read on a backend whose protocol implementation had no layered support,
|
|
581
|
+
* answering the `{ type, name, item }` envelope. A dedicated path refuses to
|
|
582
|
+
* answer a different resource under this one's declared shape, so it returns
|
|
583
|
+
* 501 `NOT_IMPLEMENTED` instead — which surfaces here as a thrown error
|
|
584
|
+
* rather than a view with `code` and `overlay` silently blank.
|
|
498
585
|
*/
|
|
499
586
|
layered<T = unknown>(type: string, name: string, options?: {
|
|
500
587
|
packageId?: string;
|
|
@@ -530,7 +617,6 @@ declare class MetadataClient {
|
|
|
530
617
|
* overlay moved while the draft was sitting.
|
|
531
618
|
*/
|
|
532
619
|
publish<T = unknown>(type: string, name: string, options?: {
|
|
533
|
-
actor?: string;
|
|
534
620
|
message?: string;
|
|
535
621
|
}): Promise<T>;
|
|
536
622
|
/**
|
|
@@ -542,7 +628,6 @@ declare class MetadataClient {
|
|
|
542
628
|
* when the target version is a delete tombstone.
|
|
543
629
|
*/
|
|
544
630
|
rollback<T = unknown>(type: string, name: string, toVersion: number, options?: {
|
|
545
|
-
actor?: string;
|
|
546
631
|
message?: string;
|
|
547
632
|
}): Promise<T>;
|
|
548
633
|
/**
|
|
@@ -747,66 +832,6 @@ interface MetadataCacheStats {
|
|
|
747
832
|
hitRate: number;
|
|
748
833
|
}
|
|
749
834
|
|
|
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
835
|
/**
|
|
811
836
|
* ObjectStack-backed `UserDataAdapter` factory.
|
|
812
837
|
*
|
|
@@ -886,344 +911,6 @@ interface UserDataAdapter<T> {
|
|
|
886
911
|
*/
|
|
887
912
|
declare function createObjectStackUserStateAdapter<T = unknown>(options: ObjectStackUserStateAdapterOptions): UserDataAdapter<T>;
|
|
888
913
|
|
|
889
|
-
/**
|
|
890
|
-
* ObjectUI
|
|
891
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
892
|
-
*
|
|
893
|
-
* This source code is licensed under the MIT license found in the
|
|
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.
|
|
989
|
-
*
|
|
990
|
-
* This source code is licensed under the MIT license found in the
|
|
991
|
-
* LICENSE file in the root directory of this source tree.
|
|
992
|
-
*/
|
|
993
|
-
/**
|
|
994
|
-
* Security module integration for @objectstack/spec v3.0.0
|
|
995
|
-
* Provides advanced security policies: CSP config, audit logging, data masking.
|
|
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.
|
|
1001
|
-
*
|
|
1002
|
-
* NOT the spec's `SecurityPolicy` (`@objectstack/spec/kernel`), whose name this
|
|
1003
|
-
* interface wore until objectui#3160 (objectstack#4115 ledger batch 6). That one
|
|
1004
|
-
* is the PACKAGE SUPPLY-CHAIN policy — `{ id, name, autoScan, thresholds,
|
|
1005
|
-
* allowedLicenses, prohibitedLicenses, codeSigning, sandbox }` — the rules a
|
|
1006
|
-
* marketplace applies when scanning a plugin before install. It shares no key
|
|
1007
|
-
* with this one, so there was nothing to derive; the collision is the word
|
|
1008
|
-
* "security", not the concept.
|
|
1009
|
-
*/
|
|
1010
|
-
interface SecurityManagerPolicy {
|
|
1011
|
-
/** Content Security Policy configuration */
|
|
1012
|
-
csp?: CSPConfig;
|
|
1013
|
-
/** Audit logging configuration */
|
|
1014
|
-
auditLog?: AuditLogConfig;
|
|
1015
|
-
/** Data masking rules */
|
|
1016
|
-
dataMasking?: DataMaskingConfig;
|
|
1017
|
-
}
|
|
1018
|
-
interface CSPConfig {
|
|
1019
|
-
/** Script sources */
|
|
1020
|
-
scriptSrc: string[];
|
|
1021
|
-
/** Style sources */
|
|
1022
|
-
styleSrc: string[];
|
|
1023
|
-
/** Image sources */
|
|
1024
|
-
imgSrc: string[];
|
|
1025
|
-
/** Connect sources (APIs, WebSockets) */
|
|
1026
|
-
connectSrc: string[];
|
|
1027
|
-
/** Font sources */
|
|
1028
|
-
fontSrc: string[];
|
|
1029
|
-
/** Frame sources */
|
|
1030
|
-
frameSrc?: string[];
|
|
1031
|
-
/** Report URI for violations */
|
|
1032
|
-
reportUri?: string;
|
|
1033
|
-
}
|
|
1034
|
-
interface AuditLogConfig {
|
|
1035
|
-
/** Whether audit logging is enabled */
|
|
1036
|
-
enabled: boolean;
|
|
1037
|
-
/** Events to audit */
|
|
1038
|
-
events: AuditEventType[];
|
|
1039
|
-
/** Log retention days */
|
|
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.
|
|
1080
|
-
*/
|
|
1081
|
-
declare class SecurityManager {
|
|
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
|
-
}
|
|
1114
|
-
|
|
1115
|
-
/**
|
|
1116
|
-
* ObjectUI
|
|
1117
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
1118
|
-
*
|
|
1119
|
-
* This source code is licensed under the MIT license found in the
|
|
1120
|
-
* LICENSE file in the root directory of this source tree.
|
|
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.
|
|
1204
|
-
*/
|
|
1205
|
-
declare function createDefaultCanvasConfig(overrides?: Partial<StudioCanvasConfig>): StudioCanvasConfig;
|
|
1206
|
-
/**
|
|
1207
|
-
* Snap a position to the grid.
|
|
1208
|
-
*/
|
|
1209
|
-
declare function snapToGrid(x: number, y: number, gridSize: number): {
|
|
1210
|
-
x: number;
|
|
1211
|
-
y: number;
|
|
1212
|
-
};
|
|
1213
|
-
/**
|
|
1214
|
-
* Calculate auto-layout positions for a set of items.
|
|
1215
|
-
* Uses a simple grid-based layout algorithm.
|
|
1216
|
-
*/
|
|
1217
|
-
declare function calculateAutoLayout(items: Array<{
|
|
1218
|
-
id: string;
|
|
1219
|
-
width: number;
|
|
1220
|
-
height: number;
|
|
1221
|
-
}>, canvasWidth: number, padding?: number, gap?: number): Array<{
|
|
1222
|
-
id: string;
|
|
1223
|
-
x: number;
|
|
1224
|
-
y: number;
|
|
1225
|
-
}>;
|
|
1226
|
-
|
|
1227
914
|
/**
|
|
1228
915
|
* Map human-readable filter operator names produced by SDUI view configs
|
|
1229
916
|
* (e.g. `lead.view.ts`) to the canonical operator symbols expected by the
|
|
@@ -1416,10 +1103,97 @@ declare class AnalyticsNotInstalledError extends Error {
|
|
|
1416
1103
|
readonly code = "ANALYTICS_NOT_INSTALLED";
|
|
1417
1104
|
/** The surface that was unavailable, for the message a host renders. */
|
|
1418
1105
|
readonly surface: string;
|
|
1419
|
-
|
|
1106
|
+
/**
|
|
1107
|
+
* The server's own ADR-0112 code — the field this branch was chosen BY, when
|
|
1108
|
+
* a producer named one (`NOT_IMPLEMENTED` from the mounted route with no
|
|
1109
|
+
* service behind it, `ROUTE_NOT_FOUND` from the dispatcher when the route is
|
|
1110
|
+
* not mounted at all). Absent for the no-code residual, where a bare
|
|
1111
|
+
* transport 404/501 is all there was to read.
|
|
1112
|
+
*
|
|
1113
|
+
* Additive (objectui#5663): carried so a reader can audit that the headline
|
|
1114
|
+
* and the quoted `detail` below came off the same answer.
|
|
1115
|
+
*/
|
|
1116
|
+
readonly serverCode?: string;
|
|
1117
|
+
constructor(surface: string, detail?: string, serverCode?: string);
|
|
1420
1118
|
}
|
|
1421
1119
|
/** True when `error` is an {@link AnalyticsNotInstalledError} (or its wire twin). */
|
|
1422
1120
|
declare function isAnalyticsNotInstalledError(error: unknown): boolean;
|
|
1121
|
+
/**
|
|
1122
|
+
* Thrown when the dataset route ANSWERED and said the dataset this query named
|
|
1123
|
+
* does not exist in this environment — `404` + ADR-0112 `NOT_FOUND`, the
|
|
1124
|
+
* `body.datasetName` lookup miss in `@objectstack/rest`'s
|
|
1125
|
+
* `registerAnalyticsEndpoints` (`Dataset "<name>" not found.`).
|
|
1126
|
+
*
|
|
1127
|
+
* A SIBLING of {@link AnalyticsNotInstalledError}, never a shade of it
|
|
1128
|
+
* (objectui#5663). Both conditions answer **404** — routes-not-mounted through
|
|
1129
|
+
* the runtime dispatcher's `ROUTE_NOT_FOUND`, an unknown dataset through this
|
|
1130
|
+
* route's own `NOT_FOUND` — so a `res.status === 404` test cannot separate
|
|
1131
|
+
* them, and the mapping that could not tell them apart reported EVERY unknown
|
|
1132
|
+
* dataset as a missing server capability.
|
|
1133
|
+
*
|
|
1134
|
+
* Measured live on a prod tenant: four HotCRM Executive Overview widgets told
|
|
1135
|
+
* the operator to install `@objectstack/service-analytics` and mount
|
|
1136
|
+
* `AnalyticsServicePlugin`, while the analytics service was installed and
|
|
1137
|
+
* answering the whole time. The real condition was an installed
|
|
1138
|
+
* `app.objectstack.hotcrm` pinned at 1.3.0 whose datasets ship in 2.2.2. The
|
|
1139
|
+
* remedy the banner named — install a server plugin — is the opposite corner of
|
|
1140
|
+
* the system from the remedy that works: upgrade the installed app. A wrong
|
|
1141
|
+
* diagnosis is not a smaller version of no diagnosis; it spends the operator's
|
|
1142
|
+
* time in the wrong subsystem.
|
|
1143
|
+
*
|
|
1144
|
+
* The `code` is the ONLY field that separates the two, which is why this branch
|
|
1145
|
+
* reads it and nothing else. See `readAnalyticsErrorEnvelope` below — the
|
|
1146
|
+
* module-private reader that pulls the code out of either declared envelope.
|
|
1147
|
+
*/
|
|
1148
|
+
declare class AnalyticsDatasetNotFoundError extends Error {
|
|
1149
|
+
readonly code = "ANALYTICS_DATASET_NOT_FOUND";
|
|
1150
|
+
/**
|
|
1151
|
+
* The dataset this query asked for, taken from the REQUEST rather than parsed
|
|
1152
|
+
* back out of the server's prose: the request is what we know for certain,
|
|
1153
|
+
* and re-reading the name out of a message would make the headline depend on
|
|
1154
|
+
* that message's WORDING — the coupling framework#5367 spent a whole issue
|
|
1155
|
+
* removing from the producing route.
|
|
1156
|
+
*/
|
|
1157
|
+
readonly datasetName?: string;
|
|
1158
|
+
/** The server's own ADR-0112 code — the field this branch was chosen BY. */
|
|
1159
|
+
readonly serverCode?: string;
|
|
1160
|
+
/** The server's own message, quoted verbatim in the parenthetical. */
|
|
1161
|
+
readonly serverMessage?: string;
|
|
1162
|
+
constructor(opts: {
|
|
1163
|
+
datasetName?: string;
|
|
1164
|
+
serverCode?: string;
|
|
1165
|
+
serverMessage?: string;
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Thrown when the dataset query was refused before it ran because the session
|
|
1170
|
+
* is not authenticated — `401` + ADR-0112 `UNAUTHENTICATED`, the REST seam's
|
|
1171
|
+
* `ANONYMOUS_DENY_BODY` (`@objectstack/core`'s `security/anonymous-deny.ts`,
|
|
1172
|
+
* written verbatim by `enforceAuth`).
|
|
1173
|
+
*
|
|
1174
|
+
* The THIRD branch, and the one the reported card under-states (objectui#5663):
|
|
1175
|
+
* it recorded a live `POST /api/v1/analytics/dataset/query -> 401
|
|
1176
|
+
* UNAUTHENTICATED` and read it as evidence for the dataset-unknown branch. It
|
|
1177
|
+
* is neither of the other two. An expired session reported as "the deployment
|
|
1178
|
+
* is missing a capability" is the SAME defect wearing a different mask — an
|
|
1179
|
+
* operator sent to install a server plugin because their token lapsed — so the
|
|
1180
|
+
* separation is the point, not a nicety.
|
|
1181
|
+
*
|
|
1182
|
+
* Deliberately not left to the generic `Dataset query failed: 401 …` either:
|
|
1183
|
+
* that string names a transport status where a person needs an action, and
|
|
1184
|
+
* "sign in again" is an action.
|
|
1185
|
+
*/
|
|
1186
|
+
declare class AnalyticsUnauthenticatedError extends Error {
|
|
1187
|
+
readonly code = "ANALYTICS_UNAUTHENTICATED";
|
|
1188
|
+
/** The server's own ADR-0112 code — the field this branch was chosen BY. */
|
|
1189
|
+
readonly serverCode?: string;
|
|
1190
|
+
/** The server's own message, quoted verbatim in the parenthetical. */
|
|
1191
|
+
readonly serverMessage?: string;
|
|
1192
|
+
constructor(opts?: {
|
|
1193
|
+
serverCode?: string;
|
|
1194
|
+
serverMessage?: string;
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1423
1197
|
/**
|
|
1424
1198
|
* Thrown when the server REJECTED the analytics query body (HTTP 400 —
|
|
1425
1199
|
* `VALIDATION_FAILED` since framework#4010 validates `/analytics/query` at the
|
|
@@ -1442,25 +1216,73 @@ declare class AnalyticsQueryRejectedError extends Error {
|
|
|
1442
1216
|
*
|
|
1443
1217
|
* `@objectstack/client`'s fetch wrapper throws on a non-2xx, decorating the
|
|
1444
1218
|
* 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
|
-
*
|
|
1219
|
+
* ADR-0112 / framework#3842 shape this repo already reads elsewhere).
|
|
1220
|
+
*
|
|
1221
|
+
* ## The `code` is the contract; the status is a transport fact (objectui#5721)
|
|
1222
|
+
*
|
|
1223
|
+
* What stood here tested `status === 404 || status === 501` BEFORE the code
|
|
1224
|
+
* operands that followed them, so the status short-circuited every one of
|
|
1225
|
+
* them: any 404 on this face was `not-installed` whatever code it carried, and
|
|
1226
|
+
* `NOT_IMPLEMENTED` / `ROUTE_NOT_FOUND` were unreachable for the very
|
|
1227
|
+
* conditions they name. Three unrelated conditions answer 404 on this url —
|
|
1228
|
+
*
|
|
1229
|
+
* route absent 404 `ROUTE_NOT_FOUND` (runtime dispatcher, framework#4019)
|
|
1230
|
+
* cube unknown 404 `CUBE_NOT_FOUND` (service-analytics' inference gate)
|
|
1231
|
+
* object unknown 404 `OBJECT_NOT_FOUND` (the `/data` fallback's own answer)
|
|
1232
|
+
*
|
|
1233
|
+
* — so a misspelled cube read as "this deployment has no analytics", told the
|
|
1234
|
+
* operator to install a server plugin, and re-answered the chart from a
|
|
1235
|
+
* different code path. Same defect as objectui#5663, arrived at from the other
|
|
1236
|
+
* direction: the mapping read a field it did not quote. Branch on the `code`;
|
|
1237
|
+
* consult the status only as a residual, when the answer declared no code at
|
|
1238
|
+
* all. Comparisons go through `errorCodeIs`/`errorCodeIsAnyOf` — the pre- and
|
|
1239
|
+
* post-ADR-0112 spellings both have to match (`@object-ui/types`).
|
|
1240
|
+
*
|
|
1241
|
+
* ### Both envelope families reach this function already decorated (MEASURED)
|
|
1242
|
+
*
|
|
1243
|
+
* `/analytics/query` exits through `@objectstack/runtime`'s
|
|
1244
|
+
* `dispatcher-plugin.errorResponseBase`, i.e. the **wrapped** `{ success:
|
|
1245
|
+
* false, error: { code, message } }` shape — not the flat one the dataset
|
|
1246
|
+
* route writes — and its 401 is `enforceAuth`'s **flat** `ANONYMOUS_DENY_BODY`.
|
|
1247
|
+
* The client's fetch wrapper flattens BOTH before throwing:
|
|
1248
|
+
* `errorBody?.code ?? errorBody?.error?.code` → `error.code`, plus
|
|
1249
|
+
* `error.httpStatus = res.status`. So no envelope reading belongs here; unlike
|
|
1250
|
+
* `queryDataset` (which owns its `fetch` and must read the body itself), this
|
|
1251
|
+
* function is handed the already-flattened error and reads one field.
|
|
1252
|
+
*
|
|
1253
|
+
* ## Each branch names ONE of the three outcomes the caller can take
|
|
1254
|
+
*
|
|
1255
|
+
* - **`not-installed`** — *degrade LOUDLY*. The deployment has no analytics
|
|
1256
|
+
* service: 501 `NOT_IMPLEMENTED` (route mounted, nothing behind it) or 404
|
|
1257
|
+
* `ROUTE_NOT_FOUND` (framework#4019 stops mounting the routes at all).
|
|
1258
|
+
* A client-side aggregate over a scoped `find()` answers the chart
|
|
1259
|
+
* correctly, and the operator is told once that the semantic layer is off.
|
|
1260
|
+
* - **`rejected`** — *THROW*. The server refused OUR body (400
|
|
1261
|
+
* `VALIDATION_FAILED`; framework#4010 validates `/analytics/query` at the
|
|
1262
|
+
* entry). Degrading would answer our own contract violation with plausible
|
|
1263
|
+
* numbers from a different code path and bury it — the misdirection
|
|
1264
|
+
* framework#3878 documented.
|
|
1265
|
+
* - **`unauthenticated`** — *THROW*. 401 `UNAUTHENTICATED`: the request was
|
|
1266
|
+
* refused before it ran, so it is evidence about the SESSION and none at
|
|
1267
|
+
* all about the capability. Degrading is not merely misleading here, it is
|
|
1268
|
+
* futile: the fallback's `find()` carries the same lapsed token and is
|
|
1269
|
+
* refused the same way, so the chart cannot be answered either. "Sign in
|
|
1270
|
+
* again" is an action; "install @objectstack/service-analytics" is not.
|
|
1271
|
+
* - **`cube-not-found`** — *THROW*. 404 `CUBE_NOT_FOUND`
|
|
1272
|
+
* (`analytics-service.assertInferableCube`, framework#3867): analytics IS
|
|
1273
|
+
* installed and answering — the NAME does not exist. That gate throws only
|
|
1274
|
+
* when the name is neither a registered cube nor a registered object, and
|
|
1275
|
+
* the fallback asks `/data/<the same name>`, which objectql's
|
|
1276
|
+
* `assertObjectRegistered` (framework#3770) answers 404 `OBJECT_NOT_FOUND`.
|
|
1277
|
+
* So degrading cannot produce numbers; it can only swap a message that
|
|
1278
|
+
* names the fix ("Define a Cube in your stack, or check the object name")
|
|
1279
|
+
* for a distant one, behind a warning that instructs the wrong repair.
|
|
1280
|
+
* - **`unknown`** — *degrade SILENTLY*. Anything else (5xx, network, a code
|
|
1281
|
+
* this consumer does not know): a transient failure, not a deployment
|
|
1282
|
+
* missing a capability and not a request that named something absent.
|
|
1461
1283
|
*/
|
|
1462
1284
|
declare function classifyAnalyticsFailure(error: unknown): {
|
|
1463
|
-
kind: 'not-installed' | 'rejected' | 'unknown';
|
|
1285
|
+
kind: 'not-installed' | 'rejected' | 'unauthenticated' | 'cube-not-found' | 'unknown';
|
|
1464
1286
|
code?: string;
|
|
1465
1287
|
message?: string;
|
|
1466
1288
|
};
|
|
@@ -1630,6 +1452,85 @@ type WriteWarningListener = (event: WriteWarningEvent) => void;
|
|
|
1630
1452
|
* prevent. Identity only — it names no cache key.
|
|
1631
1453
|
*/
|
|
1632
1454
|
declare function viewItemObjectName(item: any): string | undefined;
|
|
1455
|
+
/**
|
|
1456
|
+
* The keys a personalization overlay row legitimately OWNS (objectui#5233).
|
|
1457
|
+
*
|
|
1458
|
+
* One per `persistViewPatch` call site in app-shell's `ObjectView` — the ONLY
|
|
1459
|
+
* production writer of these rows — read off the tree rather than recalled:
|
|
1460
|
+
* `rowHeight` (the density toggle, spec-canonical since #2890), `sort`,
|
|
1461
|
+
* `hiddenFields`, `columnState` and `inlineEdit`. Nothing else in such a row
|
|
1462
|
+
* is an opinion the user expressed; anything else it carries is a COPY of the
|
|
1463
|
+
* source view as it stood at write time, because `persistViewPatch` USED TO
|
|
1464
|
+
* send `{ ...baseViewDef, ...patch }` and this adapter persists what it is
|
|
1465
|
+
* given.
|
|
1466
|
+
*
|
|
1467
|
+
* That copy was the defect the maintainer ruled on (objectstack#7494, comment
|
|
1468
|
+
* 5261754173): an overlay written by a mere column drag froze the view's
|
|
1469
|
+
* effective `filter` — and its `columns`, `label`, `type`, `isDefault` … — as
|
|
1470
|
+
* of that moment, and because the display merge is `{ ...source, ...override }`
|
|
1471
|
+
* the frozen copy SHADOWED the source view forever. An admin then edited the
|
|
1472
|
+
* view's filter and every user who once resized a column kept the old one,
|
|
1473
|
+
* with nothing anywhere reporting it.
|
|
1474
|
+
*
|
|
1475
|
+
* Both halves of that ruling have now landed, and this adapter's behaviour is
|
|
1476
|
+
* unchanged by either — it still persists what it is given:
|
|
1477
|
+
*
|
|
1478
|
+
* - **read** (PR #5272, {@link narrowPersonalizationOverlay}): the consumer
|
|
1479
|
+
* that MERGES an overlay over a source view contributes only these keys, so
|
|
1480
|
+
* every already-stored fat row stops shadowing its source.
|
|
1481
|
+
* - **write** (objectui#5233, `buildPersistedViewBody` in app-shell's
|
|
1482
|
+
* `ObjectView`, unblocked by `columnState`'s admission to the view-metadata
|
|
1483
|
+
* surface as a runtime-only overlay key — objectstack#9933, released in
|
|
1484
|
+
* `@objectstack/spec` 17.1.0): a *system view's* overlay is now written as
|
|
1485
|
+
* the patch alone, so no new row freezes anything, and because the write is
|
|
1486
|
+
* a whole-document PUT the next toggle also strips an old fat row. A *saved
|
|
1487
|
+
* view's* own row is deliberately still written whole — for it the body IS
|
|
1488
|
+
* the view, not a copy of one.
|
|
1489
|
+
*
|
|
1490
|
+
* A fat row is therefore a legacy shape, not a shape this product still
|
|
1491
|
+
* produces; the list below is still what a reader is allowed to trust from one.
|
|
1492
|
+
*
|
|
1493
|
+
* ⛔ Do not grow this list to make some other key "stick" through an overlay.
|
|
1494
|
+
* A key that belongs to the view belongs in the view; the overlay is a patch,
|
|
1495
|
+
* and a patch that carries the whole document is what this list exists to
|
|
1496
|
+
* stop. Adding a sixth entry is only correct alongside a sixth
|
|
1497
|
+
* `persistViewPatch` call site — {@link narrowPersonalizationOverlay} is what
|
|
1498
|
+
* a reader checks that against.
|
|
1499
|
+
*/
|
|
1500
|
+
declare const VIEW_OVERLAY_OWNED_KEYS: readonly ["rowHeight", "sort", "hiddenFields", "columnState", "inlineEdit"];
|
|
1501
|
+
/**
|
|
1502
|
+
* Reduce a personalization overlay row to the keys it owns — the read-side
|
|
1503
|
+
* half of objectui#5233, and the half that reaches rows ALREADY STORED.
|
|
1504
|
+
*
|
|
1505
|
+
* Rows written before this shipped carry the whole source view (see
|
|
1506
|
+
* {@link VIEW_OVERLAY_OWNED_KEYS}). Of the three dispositions the issue names
|
|
1507
|
+
* for them — strip on next write, migrate, tolerate on read — this is the
|
|
1508
|
+
* third, chosen deliberately and stated here rather than left implicit,
|
|
1509
|
+
* because it is the only one that is already true for every existing row the
|
|
1510
|
+
* moment it ships: strip-on-next-write heals a row only when its user happens
|
|
1511
|
+
* to touch that view again (and leaves the frozen filter live until then),
|
|
1512
|
+
* and a migration needs a runner this product does not have for `sys_metadata`
|
|
1513
|
+
* rows an operator may not even know exist. What the issue forbids is SILENT
|
|
1514
|
+
* tolerance; this is the explicit, pinned kind (`viewOverlayPatchOnly.test.ts`
|
|
1515
|
+
* in this package, `ObjectView.overlayPatchOnly.test.tsx` in app-shell).
|
|
1516
|
+
*
|
|
1517
|
+
* Applied by the consumer that MERGES an override over a source view
|
|
1518
|
+
* (app-shell's `sanitizeViewOverride`, the one seam both of
|
|
1519
|
+
* `loadViewOverrides`' read branches pass through), NOT by
|
|
1520
|
+
* {@link ObjectStackAdapter.listViewOverrides} / {@link ObjectStackAdapter.getView}:
|
|
1521
|
+
* those two answer with the stored DOCUMENT, their equality is itself pinned
|
|
1522
|
+
* ("same key space, same document" — `listViewOverrides.test.ts`), and
|
|
1523
|
+
* `InterfaceListPage` hydrates a hollow view out of that document. Narrowing
|
|
1524
|
+
* belongs where a row is read AS A PATCH, not where it is read as a row.
|
|
1525
|
+
*
|
|
1526
|
+
* Non-overlay rows — a genuine saved view's own body, which the same batch
|
|
1527
|
+
* read enumerates — are returned by REFERENCE, untouched: for those the row
|
|
1528
|
+
* IS the view, and every key on it is an opinion its author expressed.
|
|
1529
|
+
* Classification is {@link isPersonalizationOverlayRow}, the same predicate
|
|
1530
|
+
* {@link ObjectStackAdapter.listViews} excludes rows by, so a row cannot be a
|
|
1531
|
+
* saved view for one reader and an overlay for the other.
|
|
1532
|
+
*/
|
|
1533
|
+
declare function narrowPersonalizationOverlay<T>(row: T): T;
|
|
1633
1534
|
|
|
1634
1535
|
/**
|
|
1635
1536
|
* ObjectStack Data Source Adapter
|
|
@@ -2213,23 +2114,54 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2213
2114
|
*/
|
|
2214
2115
|
getView(objectName: string, viewId: string): Promise<unknown | null>;
|
|
2215
2116
|
/**
|
|
2216
|
-
* Persist a view
|
|
2217
|
-
*
|
|
2218
|
-
*
|
|
2219
|
-
*
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2222
|
-
*
|
|
2223
|
-
*
|
|
2224
|
-
*
|
|
2225
|
-
*
|
|
2226
|
-
*
|
|
2117
|
+
* Persist a toolbar-driven view config patch — density, column widths,
|
|
2118
|
+
* sort, hidden columns, inline edit. Symmetric counterpart to
|
|
2119
|
+
* {@link getView}: writes the row to the server metadata store via
|
|
2120
|
+
* `client.meta.saveItem`, then invalidates the matching cache entry so the
|
|
2121
|
+
* next {@link getView} reflects the new payload. Returns the persisted item
|
|
2122
|
+
* when the server echoes it, otherwise undefined.
|
|
2123
|
+
*
|
|
2124
|
+
* Called from exactly ONE production site — `ObjectView`'s
|
|
2125
|
+
* `persistViewPatch`, for the toolbar toggle — but that ONE call site
|
|
2126
|
+
* fires for BOTH kinds of active tab: a code-defined **system** view (no
|
|
2127
|
+
* row of its own yet) and a genuinely user-created **saved** view (already
|
|
2128
|
+
* has a row — the toggle is editing ITS OWN definition, not laying an
|
|
2129
|
+
* overlay on top of it). Which one a given call means is NOT re-derived
|
|
2130
|
+
* here from the write's shape (objectui#4227's own lesson: shape inference
|
|
2131
|
+
* on this namespace is exactly what let a system view masquerade as
|
|
2132
|
+
* saved) — the caller already knows, via the same `isSavedViewId`
|
|
2133
|
+
* classification that gates the switcher's readonly flag and its five
|
|
2134
|
+
* mutating handlers, and passes it as {@link opts.isSavedView}.
|
|
2135
|
+
*
|
|
2136
|
+
* - `isSavedView` false/omitted (system-view target, the common case and
|
|
2137
|
+
* the default for backward compatibility): stamps
|
|
2138
|
+
* {@link VIEW_OVERLAY_MARKER} so {@link listViews} excludes the row —
|
|
2139
|
+
* the original objectui#4227 fix.
|
|
2140
|
+
* - `isSavedView` true: the marker is withheld. Stamping it here would
|
|
2141
|
+
* flag the saved view's OWN row as a personalization overlay, and
|
|
2142
|
+
* `listViews()` would exclude it on the very next read — the user's own
|
|
2143
|
+
* view would vanish from the switcher the moment they toggled its
|
|
2144
|
+
* density (objectui#4227 follow-up, PM review on PR #4713, measured:
|
|
2145
|
+
* `persistViewPatch` has no gate on which kind of tab is active, and
|
|
2146
|
+
* this method writes to the exact same `(type='view', name=viewId)` key
|
|
2147
|
+
* {@link createView}/the ADR-0034 `viewEnvelope` seam already used for
|
|
2148
|
+
* that view, so the write is an upsert onto the saved view's row, not a
|
|
2149
|
+
* new one).
|
|
2150
|
+
*
|
|
2151
|
+
* Per objectstack#7494's ruling, the overlay this writes (system-view
|
|
2152
|
+
* case) is ORG-WIDE shared view settings, not a per-user preference — a
|
|
2153
|
+
* true per-user scope is a parked v18 direction on the platform side.
|
|
2227
2154
|
*
|
|
2228
2155
|
* @param objectName - Object name (e.g. 'lead')
|
|
2229
2156
|
* @param viewId - View identifier (e.g. 'all_leads')
|
|
2230
2157
|
* @param config - Full view definition to persist
|
|
2158
|
+
* @param opts.isSavedView - Whether `viewId` already names a saved view
|
|
2159
|
+
* (vs. a system view being personalized for the first time). Omit /
|
|
2160
|
+
* `false` for the default overlay-marking behavior.
|
|
2231
2161
|
*/
|
|
2232
|
-
updateViewConfig(objectName: string, viewId: string, config: Record<string, any
|
|
2162
|
+
updateViewConfig(objectName: string, viewId: string, config: Record<string, any>, opts?: {
|
|
2163
|
+
isSavedView?: boolean;
|
|
2164
|
+
}): Promise<Record<string, any> | void>;
|
|
2233
2165
|
/**
|
|
2234
2166
|
* List user-created views for a given object via the metadata overlay
|
|
2235
2167
|
* API (ADR-0005). Replaces the legacy `find('sys_view', {...})` path
|
|
@@ -2403,8 +2335,13 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
2403
2335
|
* Ask the by-name meta app route WHY an app is not in this session's app list
|
|
2404
2336
|
* (objectui#4252).
|
|
2405
2337
|
*
|
|
2406
|
-
* `GET /api/v1/meta
|
|
2407
|
-
*
|
|
2338
|
+
* The app LIST is the generic metadata list route `GET /api/v1/meta/:type`,
|
|
2339
|
+
* requested with the singular type segment `app` — the same address this
|
|
2340
|
+
* method appends a name to below, and the one `MetadataProvider` reads its
|
|
2341
|
+
* items from. The server filters that list per session in `filterAppForUser`
|
|
2342
|
+
* (`packages/rest/src/rest-server.ts`, applied inside the `:type` list handler
|
|
2343
|
+
* once the type segment resolves to `app`), so an app withheld by
|
|
2344
|
+
* `requiredPermissions` and an app
|
|
2408
2345
|
* that does not exist are byte-identical there: both are simply absent. A
|
|
2409
2346
|
* console reading only that list has one fact and two conditions, and it
|
|
2410
2347
|
* renders its copy for the wrong one — "it may still be publishing" over a
|
|
@@ -2672,4 +2609,4 @@ declare function createObjectStackAdapter<T = unknown>(config: {
|
|
|
2672
2609
|
reconnectDelay?: number;
|
|
2673
2610
|
}): DataSource<T>;
|
|
2674
2611
|
|
|
2675
|
-
export { API_ACCESS_DENIED_CODES, APP_PERMISSION_DENIED_CODE, AnalyticsNotInstalledError, AnalyticsQueryRejectedError,
|
|
2612
|
+
export { API_ACCESS_DENIED_CODES, APP_PERMISSION_DENIED_CODE, AnalyticsDatasetNotFoundError, AnalyticsNotInstalledError, AnalyticsQueryRejectedError, AnalyticsUnauthenticatedError, type AppAccessVerdict, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, ConcurrentUpdateError, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, DataApiValidationError, FILTER_OPERATOR_ALIASES, 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 MetadataReference, type MetadataSaveAdvisoryEvent, type MetadataSaveAdvisoryListener, type MetadataValidationIssue, ObjectStackAdapter, ObjectStackError, type ObjectStackUserStateAdapterOptions, type UserDataAdapter, VIEW_OVERLAY_OWNED_KEYS, type WriteWarningEvent, type WriteWarningListener, classifyAnalyticsFailure, clearSharedDiscoveryCache, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, createQuietHttpLogger, formatHttpFailureMessage, getSharedDiscovery, is404Error, isAnalyticsNotInstalledError, isApiAccessDeniedError, isAppPermissionDeniedError, isConcurrentUpdateError, isErrorType, isMalformedFilterError, isObjectStackError, narrowPersonalizationOverlay, normaliseClientError, readSaveAdvisories, readTransactionalBatchCapability, serializeOrderBy, viewItemObjectName };
|