Skip to content

Commit 3e04799

Browse files
perf(app-shell): Home's navigation roster gate asks its required objects in one ?names= request (#30864)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoHb1GehvoiX4DcV3e24Gypu)_ Fixes #29444 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:Console 首页(应用外)导航名册的「对象是否存在」门禁,现在把所有 `requiresObject` 对象名合成一次 `GET /meta/object?names=a,b,c` 读取,不再每个对象名发一次请求。 - 为什么改:app-crm 首页每次打开都要为这道门禁发 9 个对象元数据请求;服务端 `?names=` 批量读(#29236 裁决 A,PR #30709)已上线,实测改后只剩 1 个。 - 风险与回滚:只影响应用外(首页等没有当前应用的页面);应用内仍逐个按名读,记录表单继续与数据适配器共用那次读取。批量答案里缺的名字按 404 处理(入口隐藏),请求失败则所有名字都算未答(入口照常显示),与原来逐个读的行为一致。回滚即还原本 PR。 - 先看:`ui/packages/app-shell/src/providers/MetadataProvider.tsx` 的 `fetchScopeNames`(缺名、失败、刷新三种情形);`ui/packages/app-shell/src/hooks/useNavMemberGates.ts` 里 `outsideApp` 那一处分支;`ui/packages/app-shell/src/console/home/__tests__/homeObjectReads-29121.test.tsx`。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/console/home/__tests__/homeObjectReads-29121.test.tsx`,预期 7 个用例全绿,其中门禁用例只记到一条 `object?names=sys_cloud_only,sys_queue` 请求。 - 需要你做的:无,席位落地。 - **席位意见**:ui 席 2 对照 head `382ce259` 的 diff 复核:5 个文件、+221 / −26,只在 app 外(首页)把路由门要的对象并成一次 `GET /meta/object?names=` 读;缺名照旧算未注册(同按名 404),整次读失败则每个名都算未答(同按名读失败),刷新再并读一次,预览与 app 内仍按名读,路由读者不会拿列表回答当自己的。`MetadataContextValue` 类型未改(钩子内本地类型转交 `batch`),无新导出,Clause-② no 成立,不欠契约复审;382ce259 只改测试以过 Lint UI。CI 全绿,同意落地。 ## What changes - **The roster gate outside an app asks once.** `useNavMemberGates` reads the app list's `requiresObject` union through the names-only scope. Outside an app (a frame whose app has no `name`: Home, or a frame with no expression scope), it now passes a package-local `batch: true` hand-off beside `views: false`. Inside an app nothing changes: the union is still read by name, because the route's own objects are among those names and their by-name read is shared with the adapter's `getObjectSchema` cache (objectui#13044). - **The provider answers the hand-off from its own scope entry.** `MetadataProvider.readObjectScope` keys a batched read under `NAMES_BATCH_SCOPE`, a symbol, so no package id can collide with it. It never shares the names-only entry: a list answer carries no by-name envelope and cannot tell an absent object from a refused one (`OBJECT_PACKAGE_DISABLED`), so a route reader never takes it for its own. The names not asked yet go out in one `meta.getItems('object', { names })` (the client door PR #30709 added; no new URL builder). - A name in the answer is answered with its definition, through the same list ingestion as every object list (`ingestObjectList`). - A name the answer leaves out is answered missing, exactly as a by-name `404` was (ruling A on #29236, 6580261740: an unknown name is simply absent). - A read that gets no answer fails every name it asked, as a failed by-name read failed its one (objectui#13009, objectui#13052). The gate shows those entries and does not ask again until `invalidate`, `refresh` or an organization switch. - Only the asked names are read out of the answer, so an item the request did not name never lands in the scope. - `refresh` asks the batched names again in one request. - Under the draft preview, whose `MetadataClient.list` has no names filter, the hand-off falls back to the names-only scope (by name). - **Same filtering as the by-name read (measured on `main` b618224).** The list door runs `?names=` after the per-caller list gate (whose `object` arm is the by-name read's `filterObjectListViewsForUser`) and before the ADR-0106 mask and translation. The store read drops a disabled package's objects, where the by-name read refuses them `404 OBJECT_PACKAGE_DISABLED`. The gate reads both answers as "not registered". The answer is the `{ type, items: [...] }` array envelope, which `extractItems` already reads. - No new export from `@object-ui/app-shell`. The hand-off type `ReadNamesInOneRequest` is local to `useNavMemberGates.ts`. ## Tests - `homeObjectReads-29121.test.tsx` (real `MetadataProvider` + real gate, fake adapter): - Outside an app, the gate sends one `object?names=sys_cloud_only,sys_queue` request. The absent `sys_cloud_only` is hidden and `sys_queue` is shown. This replaces the two by-name reads the case pinned before. - A batch that rejects leaves both entries shown and is not asked again. - `refresh('object')` re-asks the names in one request. - `navGateUnionScope-12980.test.tsx`: the outside-app case asserts the single `?names=` request for the five-name union. Its fake adapter now honours `names` the way the door does: it keeps those items, and an unknown name is absent. The package-less-app cases (inside an app, by name) are unchanged and green. - Ablations, each through `scripts/ablation-replace.mjs`, which confirmed the mutation landed and the restore matched the HEAD blob: - gate hand-off off (`outsideApp = false`): 2 cases red; - absent name not recorded missing: 1 red; - refresh asks by name: 1 red. <details><summary>Related suite (347 files that import the provider or the gate)</summary> `pnpm --dir ui exec vitest run --maxWorkers=2` over every app-shell / console test that names `readObjectScope`, `useNavMemberGates` or `MetadataProvider`, before the union-scope pin below was updated. 345 files passed and 2 failed: - `navGateUnionScope-12980` (outside-app case): its fake adapter ignored `names`. Updated above, and the file is now 12/12 with the Home file. - `spec-symbol-parity.test.ts`: 16 cases failed because the probe reads `@objectstack/spec`'s `.d.ts` files, and that run's build used `OS_SKIP_DTS=1` (`SPEC_NAMES.size` 0). After a declarations build, the file is green together with the two gate files: 3 files, 53 tests. </details> ## Verification Live, on app-crm. Backend `objectstack dev --seed-admin --fresh -p 4417` in `examples/app-crm`. Console `vite --port 5197 --strictPort` with `DEV_PROXY_TARGET` pointed at it. Headless Chromium signed in as the seeded admin, then loaded `/home` fresh, counting every request whose path starts with `/api/v1/meta/object`. | Tree | Roster gate requests | Other object reads on Home | |---|---|---| | `origin/main` (the two source files restored from b618224) | 9: `GET /meta/object/NAME` for `sys_assignment_rule`, `sys_business_unit`, `sys_notification`, `sys_notification_preference`, `sys_notification_template`, `sys_queue`, `sys_queue_member`, `sys_record_share`, `sys_sharing_rule` | 1 (`sys_activity`, the activity feed's presence check) | | this branch | **1**: `GET /meta/object?names=` with the same nine names, 200, 9 items | 1 (`sys_activity`, unchanged) | The card counted 8 names on app-crm. The app list now carries 9. <details><summary>Gates</summary> At head `c2ce30e3`. `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 36 families from this diff; each ran with stdin closed, and the exit code was captured before any pipe. `--ran` reconciliation: 36 derived, 36 run, 0 NOT-MEASURED, 0 UNRUN. ``` node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 node scripts/ci/snapshot-next.mjs --self-test :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 pnpm --dir ui exec vitest run scripts/ :: exit 0 pnpm --dir ui run check:new-line-citations :: exit 0 pnpm --filter @object-ui/app-shell type-check :: exit 0 ``` `pnpm --filter @object-ui/app-shell type-check` (both `tsc --noEmit` and `tsconfig.test.json`) ran against a forced declarations build of `@object-ui/app-shell^...`. `node scripts/check-sdui-manifest.mjs` is in the derived list above. </details> ## Acceptance notes - `sys_activity`'s by-name read on Home belongs to `useObjectPresence` (the activity feeds), not to the roster gate. It is left as it is: one name, and its by-name read is the one the adapter cache shares. - Inside an app with no package, the gate still reads the union by name (objectstack#29416's path). Folding it too would cost the record form's shared by-name read (objectui#13044) for the route's own objects. That is not this card's acceptance, so it is noted here and not changed. - Under the draft preview the gate keeps its by-name reads, because `MetadataClient.list` in `@object-ui/data-objectstack` has no `names` option. Adding one is a data-adapter change this card does not need. <!-- signed: domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7145bf3 commit 3e04799

5 files changed

Lines changed: 221 additions & 26 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
perf(app-shell): Home's navigation roster gate asks the objects it requires in one `GET /meta/object?names=a,b,c` request, instead of one by-name read per object
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Outside an app (Home, whose activity feeds gate every app's navigation), the runtime-capability gate for `requiresObject` entries read each required object by name, one `GET /meta/object/NAME` per name (eight on app-crm). It now asks the names it has not asked yet in one `?names=` request (`meta.getItems('object', { names })`). A name the answer leaves out is treated as not registered, exactly as a by-name `404` was, so its entry stays hidden. A request that gets no answer leaves every name unanswered, so each entry shows, as when a by-name read failed. A refresh asks the same names again in one request. Inside an app nothing changes: the gate still reads the union by name, so the record form keeps sharing those reads with the data adapter's cache. Under the draft preview the gate also keeps the by-name reads.

‎ui/packages/app-shell/src/console/home/__tests__/homeObjectReads-29121.test.tsx‎

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
*
1818
* These cases count the reads the REAL `MetadataProvider` sends to a fake
1919
* adapter, outside an app, for each hook reader: the names each asks for, by
20-
* name, and neither list. The CONTROL: once the whole list is loaded, a reader
20+
* name, and neither list; the roster gate asks its names in one `?names=`
21+
* request (objectstack#29444). The CONTROL: once the whole list is loaded, a reader
2122
* reads it and asks nothing. `HomeLayout`'s palette and `HomePage`'s recent
2223
* rows are counted in their own files (`HomeLayout.paletteReads-29121`,
2324
* `HomePage.recentObjectReads-29121`).
@@ -26,7 +27,7 @@
2627
import '@testing-library/jest-dom/vitest';
2728
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
2829
import React from 'react';
29-
import { cleanup, render, screen, waitFor } from '@testing-library/react';
30+
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
3031
import { MetadataProvider, useMetadata } from '../../../providers/MetadataProvider';
3132
import { readObjectsByName, useObjectPresence } from '../../../hooks/useObjectPresence';
3233
import { useNavMemberGates } from '../../../hooks/useNavMemberGates';
@@ -59,7 +60,7 @@ const APPS = [
5960
},
6061
];
6162

62-
function makeAdapter(calls: string[]) {
63+
function makeAdapter(calls: string[], failBatch = false) {
6364
return {
6465
clearCache: vi.fn(),
6566
invalidateCache: vi.fn(),
@@ -70,10 +71,21 @@ function makeAdapter(calls: string[]) {
7071
},
7172
getClient: () => ({
7273
meta: {
73-
getItems: (type: string, options?: { packageId?: string }) => {
74-
calls.push(options?.packageId ? `${type}?package=${options.packageId}` : type);
74+
getItems: (type: string, options?: { packageId?: string; names?: readonly string[] }) => {
75+
calls.push(
76+
options?.packageId
77+
? `${type}?package=${options.packageId}`
78+
: options?.names
79+
? `${type}?names=${options.names.join(',')}`
80+
: type,
81+
);
82+
if (failBatch && options?.names) return Promise.reject(new Error('network down'));
7583
const all = type === 'object' ? OBJECTS : type === 'app' ? APPS : [];
76-
const items = options?.packageId ? all.filter((o: any) => o._packageId === options.packageId) : all;
84+
const items = options?.packageId
85+
? all.filter((o: any) => o._packageId === options.packageId)
86+
: options?.names
87+
? all.filter((o: any) => options.names!.includes(o.name))
88+
: all;
7789
return Promise.resolve({ type, items: structuredClone(items) });
7890
},
7991
getItem: (type: string, name: string) => {
@@ -90,9 +102,9 @@ function makeAdapter(calls: string[]) {
90102
const metaReads = (calls: string[]) =>
91103
calls.filter((c) => c === 'object' || c === 'view' || c.startsWith('object?') || c.startsWith('object/'));
92104

93-
function mount(children: React.ReactNode): string[] {
105+
function mount(children: React.ReactNode, options: { failBatch?: boolean } = {}): string[] {
94106
const calls: string[] = [];
95-
render(<MetadataProvider adapter={makeAdapter(calls)}>{children}</MetadataProvider>);
107+
render(<MetadataProvider adapter={makeAdapter(calls, options.failBatch)}>{children}</MetadataProvider>);
96108
return calls;
97109
}
98110

@@ -140,12 +152,52 @@ describe('Home asks for the objects it names, never the whole registry (objectst
140152
expect(metaReads(calls)).toEqual(['object/sys_activity']);
141153
});
142154

143-
it('outside an app, the roster’s capability gate asks the app list’s required objects by name, and answers as the whole list did', async () => {
155+
// objectstack#29444 — in ONE `?names=` request, no longer one by-name read
156+
// per name. `sys_cloud_only` is not in this deployment: the answer leaves it
157+
// out, and the gate hides its entry, as it did on the by-name read's 404.
158+
it('outside an app, the roster’s capability gate asks the app list’s required objects in one ?names= request, and answers as the whole list did', async () => {
144159
const calls = mount(<GateProbe />);
145160
await waitFor(() => expect(screen.getByTestId('gate')).toHaveAttribute('data-cloud', 'false'));
146161
expect(screen.getByTestId('gate')).toHaveAttribute('data-queue', 'true');
147162
await settle();
148-
expect([...metaReads(calls)].sort()).toEqual(['object/sys_cloud_only', 'object/sys_queue']);
163+
expect(metaReads(calls)).toEqual(['object?names=sys_cloud_only,sys_queue']);
164+
});
165+
166+
it('outside an app, a ?names= read that obtained no answer leaves every name unanswered: each entry shows, and nothing is asked again', async () => {
167+
const calls = mount(<GateProbe />, { failBatch: true });
168+
await waitFor(() => expect(metaReads(calls)).toEqual(['object?names=sys_cloud_only,sys_queue']));
169+
await settle();
170+
expect(screen.getByTestId('gate')).toHaveAttribute('data-cloud', 'true');
171+
expect(screen.getByTestId('gate')).toHaveAttribute('data-queue', 'true');
172+
expect(metaReads(calls)).toEqual(['object?names=sys_cloud_only,sys_queue']);
173+
});
174+
175+
it('outside an app, a refresh asks the roster’s names again in one ?names= request', async () => {
176+
const handle: { refresh?: (type?: string) => Promise<void> } = {};
177+
function RefreshHandle({ into }: { into: typeof handle }) {
178+
const { refresh } = useMetadata();
179+
React.useEffect(() => {
180+
into.refresh = refresh;
181+
}, [into, refresh]);
182+
return null;
183+
}
184+
const calls = mount(
185+
<>
186+
<RefreshHandle into={handle} />
187+
<GateProbe />
188+
</>,
189+
);
190+
await waitFor(() => expect(screen.getByTestId('gate')).toHaveAttribute('data-cloud', 'false'));
191+
await settle();
192+
await act(async () => {
193+
await handle.refresh!('object');
194+
});
195+
await settle();
196+
expect(screen.getByTestId('gate')).toHaveAttribute('data-cloud', 'false');
197+
expect(metaReads(calls).filter((c) => c.startsWith('object?names=') || c.startsWith('object/'))).toEqual([
198+
'object?names=sys_cloud_only,sys_queue',
199+
'object?names=sys_cloud_only,sys_queue',
200+
]);
149201
});
150202

151203
it('an activity row names its object from a read of that object alone', async () => {

‎ui/packages/app-shell/src/hooks/__tests__/navGateUnionScope-12980.test.tsx‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,24 @@ function makeAdapter(calls: string[], apps: unknown[], behaviour: Behaviour = {}
7777
},
7878
getClient: () => ({
7979
meta: {
80-
getItems: (type: string, options?: { packageId?: string }) => {
81-
calls.push(options?.packageId ? `${type}?package=${options.packageId}` : type);
82-
if (type === 'object' && !options?.packageId && behaviour.failWholeList) {
80+
getItems: (type: string, options?: { packageId?: string; names?: readonly string[] }) => {
81+
calls.push(
82+
options?.packageId
83+
? `${type}?package=${options.packageId}`
84+
: options?.names
85+
? `${type}?names=${options.names.join(',')}`
86+
: type,
87+
);
88+
if (type === 'object' && !options?.packageId && !options?.names && behaviour.failWholeList) {
8389
return Promise.reject(Object.assign(new Error('unavailable'), { httpStatus: 503 }));
8490
}
85-
const all = (type === 'object' ? OBJECTS : type === 'app' ? apps : []) as Array<{ _packageId?: string }>;
86-
const items = options?.packageId ? all.filter((o) => o._packageId === options.packageId) : all;
91+
const all = (type === 'object' ? OBJECTS : type === 'app' ? apps : []) as Array<{ name?: string; _packageId?: string }>;
92+
// objectstack#29444 — `?names=` keeps the items of those names; an unknown name is simply absent.
93+
const items = options?.packageId
94+
? all.filter((o) => o._packageId === options.packageId)
95+
: options?.names
96+
? all.filter((o) => options.names!.includes(o.name ?? ''))
97+
: all;
8798
return Promise.resolve({ type, items: structuredClone(items) });
8899
},
89100
getItem: (type: string, name: string) => {
@@ -212,14 +223,17 @@ describe('package-less app: the nav gate reads the union of required objects by
212223
expect(answers(failed(), NOTES_APP, APPS).sidebar).toEqual(['nav_note', 'nav_notes_gone']);
213224
});
214225

215-
it('objectstack#29121 — outside an app (no app name, the home feeds), the gate reads the app list’s union by name, never the whole registry', async () => {
226+
it('objectstack#29121 — outside an app (no app name, the home feeds), the gate reads the app list’s union in one ?names= request (objectstack#29444), never the whole registry', async () => {
216227
const calls: string[] = [];
217228
const read = mount({}, APPS, calls);
218229
await waitFor(() => expect(read().gates.checkCapability('object', 'legacy_gone')).toBe(false));
219230
await waitFor(() => expect(read().gates.checkCapability('object', 'notes_gone')).toBe(false));
220231

221232
expect(read().gates.checkCapability('object', 'crm_account')).toBe(true);
222233
expect(objectCalls(calls)).not.toContain('object');
234+
expect(objectCalls(calls).filter((c) => c.startsWith('object?names=') || c.startsWith('object/'))).toEqual([
235+
'object?names=area_gone,crm_account,legacy_gone,note,notes_gone',
236+
]);
223237
expect(calls).not.toContain('view');
224238
expect(read().ctx.getTypeStatus!('object')).toBe('idle');
225239
});

‎ui/packages/app-shell/src/hooks/useNavMemberGates.ts‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ import type { ReadNamesWithoutViews } from './useObjectPresence.js';
102102

103103
const NO_MISSING: readonly string[] = [];
104104

105+
/**
106+
* `readObjectScope` with the provider's package-local `batch: true` hand-off
107+
* beside `views: false` (objectstack#29444, `MetadataProvider`): the names not
108+
* asked yet are asked in one `?names=` request.
109+
*/
110+
type ReadNamesInOneRequest = (
111+
scope: { names: readonly string[]; views: false; batch: true },
112+
) => ReturnType<ReadNamesWithoutViews>;
113+
105114
/** No app requires an object: nothing is gated, and nothing is asked. */
106115
const NO_REQUIRED_OBJECTS = Object.freeze({ objects: [] as readonly unknown[], missing: NO_MISSING });
107116

@@ -383,8 +392,19 @@ export function useNavMemberGates(forApp?: Record<string, unknown>): NavMemberGa
383392
// hold, so composing them read the whole `/meta/view` list on every route
384393
// of such an app. The route's own objects are composed with their views by
385394
// the route's reader (`AppContent`), not here.
395+
//
396+
// objectstack#29444 — outside an app (a frame whose app has no `name`: the
397+
// home page, a frame that mounts no expression scope) the union is asked in
398+
// ONE `GET /meta/object?names=a,b,c` (`batch: true`, the provider's
399+
// batched names scope), not one by-name read per name. A name the answer
400+
// leaves out is missing, as a by-name 404 was; a read that obtained no
401+
// answer leaves every name unanswered, so each entry shows, as when a
402+
// by-name read failed. Inside an app the union is still asked by name: the
403+
// route's own objects are among its names, and their by-name read is the
404+
// one the record form shares with the adapter (objectui#13044).
386405
const metadata = useMetadata();
387406
const packageId = typeof app?._packageId === 'string' && app._packageId ? app._packageId : undefined;
407+
const outsideApp = !(typeof app?.name === 'string' && app.name !== '');
388408
const readObjectScope = metadata.getTypeStatus?.('object') === 'idle' ? metadata.readObjectScope : undefined;
389409
const unionNames = readObjectScope && !packageId ? requiredObjectUnion(metadata.apps, app) : NO_MISSING;
390410
const scope: { objects: readonly unknown[]; missing: readonly string[] } | null = !readObjectScope
@@ -393,7 +413,9 @@ export function useNavMemberGates(forApp?: Record<string, unknown>): NavMemberGa
393413
? (readObjectScope as ReadNamesWithoutViews)({ packageId, views: false })
394414
: unionNames.length === 0
395415
? NO_REQUIRED_OBJECTS
396-
: (readObjectScope as ReadNamesWithoutViews)({ names: unionNames, views: false });
416+
: outsideApp
417+
? (readObjectScope as ReadNamesInOneRequest)({ names: unionNames, views: false, batch: true })
418+
: (readObjectScope as ReadNamesWithoutViews)({ names: unionNames, views: false });
397419
const metadataObjects = scope ? scope.objects : metadata.objects;
398420
const missingObjects = scope ? scope.missing : NO_MISSING;
399421
const registeredObjectNames = useMemo(

0 commit comments

Comments
 (0)