|
9 | 9 | import { expandViewContainer } from '@objectstack/spec/ui'; |
10 | 10 | import { ActiveOrganizationStorage, TokenStorage, useAuth } from '@object-ui/auth'; |
11 | 11 | import { isMetaItemAbsentError, MetadataNotFoundError, type ObjectStackAdapter } from '@object-ui/data-objectstack'; |
12 | | -import { markSwitchedOffActionsByObject, normalizeSchemaReferenceKeys } from '@object-ui/core'; |
| 12 | +import { isActionSwitchedOff, markSwitchedOffActions, markSwitchedOffActionsByObject, normalizeSchemaReferenceKeys } from '@object-ui/core'; |
13 | 13 | import { errorCodeIs } from '@object-ui/types'; |
14 | 14 | import { resolveInlineMode } from '@object-ui/plugin-form'; |
15 | 15 | import { MetadataCtx, useMetadata, type MetadataContextValue, type MetadataCacheState, type MetadataTypeStatus } from '@object-ui/react'; |
@@ -360,6 +360,37 @@ export function extractItems(res: unknown): any[] { |
360 | 360 | return []; |
361 | 361 | } |
362 | 362 |
|
| 363 | +/** |
| 364 | + * The ingestion every OBJECT list read runs — the whole list and a package |
| 365 | + * scope's alike, so a scoped definition is shaped exactly like the same object |
| 366 | + * in `objects`. In place. |
| 367 | + * |
| 368 | + * 1. a legacy `reference_to` / `referenceTo` is folded onto `reference`; |
| 369 | + * 2. [objectstack#27504] the envelope's `disabledActions`, keyed by object: |
| 370 | + * each object's switched-off action entries are stamped (symbol key), so |
| 371 | + * the action gates stop offering them. |
| 372 | + */ |
| 373 | +function ingestObjectList(items: any[], res: unknown): void { |
| 374 | + for (const it of items) normalizeSchemaReferenceKeys(it); |
| 375 | + if (res && typeof res === 'object' && !Array.isArray(res)) { |
| 376 | + markSwitchedOffActionsByObject(items, (res as { disabledActions?: unknown }).disabledActions); |
| 377 | + } |
| 378 | +} |
| 379 | + |
| 380 | +/** |
| 381 | + * A private copy of an adapter-served object schema. `structuredClone` drops |
| 382 | + * symbol-keyed properties, so the switched-off stamps the adapter put on the |
| 383 | + * action entries (objectstack#27504) are carried onto the copy by name. |
| 384 | + */ |
| 385 | +function cloneServedObject(schema: unknown): unknown { |
| 386 | + const copy = structuredClone(schema); |
| 387 | + const actions = (schema as { actions?: unknown }).actions; |
| 388 | + if (Array.isArray(actions)) { |
| 389 | + markSwitchedOffActions(copy, actions.filter(isActionSwitchedOff).map((a: { name?: unknown }) => a.name)); |
| 390 | + } |
| 391 | + return copy; |
| 392 | +} |
| 393 | + |
363 | 394 | function extractItem(res: unknown): any | null { |
364 | 395 | if (res == null) return null; |
365 | 396 | if (typeof res === 'object' && 'item' in res) { |
@@ -783,7 +814,7 @@ export function MetadataProvider({ children, adapter, ttlMs = DEFAULT_TTL_MS }: |
783 | 814 | const readObjectThroughAdapter = useCallback((name: string): Promise<unknown> => { |
784 | 815 | adapterObjectNamesRef.current.add(name); |
785 | 816 | return adapterRef.current.getObjectSchema(name).then( |
786 | | - (schema: unknown) => ({ item: schema == null ? schema : structuredClone(schema) }), |
| 817 | + (schema: unknown) => ({ item: schema == null ? schema : cloneServedObject(schema) }), |
787 | 818 | (err: unknown) => { |
788 | 819 | throw err instanceof MetadataNotFoundError ? (err.details?.originalError ?? err) : err; |
789 | 820 | }, |
@@ -896,16 +927,9 @@ export function MetadataProvider({ children, adapter, ttlMs = DEFAULT_TTL_MS }: |
896 | 927 | // object field defs at ingestion (the store-side choke point, |
897 | 928 | // mirroring the adapter's getObjectSchema pass), so |
898 | 929 | // `useMetadata().objects` consumers read `reference` alone |
899 | | - // (objectui#11070 round 4). Idempotent, in place. |
900 | | - if (type === 'object') { |
901 | | - for (const it of items) normalizeSchemaReferenceKeys(it); |
902 | | - // [objectstack#27504] The list envelope's `disabledActions`, keyed |
903 | | - // by object: each object's switched-off action entries are stamped |
904 | | - // (symbol key) so the action gates stop offering them. |
905 | | - if (res && typeof res === 'object' && !Array.isArray(res)) { |
906 | | - markSwitchedOffActionsByObject(items, (res as { disabledActions?: unknown }).disabledActions); |
907 | | - } |
908 | | - } |
| 930 | + // (objectui#11070 round 4), and stamp the switched-off actions |
| 931 | + // (objectstack#27504) — `ingestObjectList`. Idempotent, in place. |
| 932 | + if (type === 'object') ingestObjectList(items, res); |
909 | 933 | entry.items = items; |
910 | 934 | entry.status = 'ready'; |
911 | 935 | entry.error = null; |
@@ -1053,7 +1077,7 @@ export function MetadataProvider({ children, adapter, ttlMs = DEFAULT_TTL_MS }: |
1053 | 1077 | .then((res: unknown) => { |
1054 | 1078 | if (scopesRef.current.get(packageId) !== entry) return; |
1055 | 1079 | const items = extractItems(res); |
1056 | | - for (const it of items) normalizeSchemaReferenceKeys(it); |
| 1080 | + ingestObjectList(items, res); |
1057 | 1081 | const listed = new Set(items.map((it: any) => it?.name)); |
1058 | 1082 | // The by-name extras stay until their own answer replaces them. |
1059 | 1083 | const extras = entry.items.filter((it: any) => !listed.has(it?.name) && entry.names.has(it?.name)); |
|
0 commit comments