teraprox-core-sdk 0.3.10 → 0.3.14

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.
@@ -78,6 +78,12 @@ interface IObservabilityPort {
78
78
  logBreadcrumb(payload: BreadcrumbPayload): void;
79
79
  }
80
80
 
81
+ interface RateLimitEntry {
82
+ used: number;
83
+ limit: number;
84
+ exceeded: boolean;
85
+ windowReset: string;
86
+ }
81
87
  interface CoreService {
82
88
  /** Cria um HttpController configurado para um contexto/endpoint */
83
89
  createController(context: string, baseEndPoint?: string): HttpController;
@@ -97,6 +103,8 @@ interface CoreService {
97
103
  hostedByCore: boolean;
98
104
  /** Porta de observabilidade — tracking, vitals e breadcrumbs */
99
105
  observability: IObservabilityPort;
106
+ /** Estado de rate limit em tempo real via RTDB. pathGroup → uso atual. */
107
+ rateLimits: Record<string, RateLimitEntry>;
100
108
  }
101
109
 
102
110
  interface FederatedBridgeProps {
@@ -123,28 +131,151 @@ interface FederatedBridgeProps {
123
131
  declare function FederatedBridge({ coreService, children }: FederatedBridgeProps): react_jsx_runtime.JSX.Element;
124
132
 
125
133
  /**
126
- * Factory for creating a standardized ReducersBundle.
134
+ * Indica se o bundle atual está sendo executado dentro do shell teraprox-core
135
+ * (Module Federation). O host define `window.__TERAPROX_HOSTED_BY_CORE__` via
136
+ * `FederatedBridge` ao montar o remote.
127
137
  *
128
- * Each remote only needs to declare ITS reducers and context map.
129
- * Resolution, deduplication, and loading logic lives here in the SDK.
138
+ * Use para ramificações raras (ex.: não montar `StandaloneProvider` duplicado).
139
+ * Para HTTP, prefira sempre `useCoreService().createController` no host vem
140
+ * do `CoreServiceProvider`; em standalone vem do `StandaloneProvider`.
141
+ */
142
+ declare function isHostedByCore(): boolean;
143
+
144
+ /**
145
+ * Dependência de reducer declarada no manifest.
130
146
  *
131
- * USAGE IN REMOTES:
132
- * ```
133
- * import { createReducersBundle } from 'teraprox-core-sdk/federation'
147
+ * Cada rota declara quais fatias do Redux precisa.
148
+ * O core injeta APENAS essas fatias antes de montar o componente.
134
149
  *
135
- * export default createReducersBundle({
136
- * reducers: {
137
- * ordemDeServico: () => import('../Reducers/osReducer'),
138
- * ordemDeManutencao: () => import('../Reducers/omReducer'),
139
- * },
140
- * contextMap: {
141
- * visaoGeral: ['ordemDeServico', 'ordemDeManutencao'],
142
- * ordemDeServico: ['ordemDeServico'],
143
- * },
144
- * defaults: ['picker', 'globalError'],
145
- * })
146
- * ```
150
+ * A string é a chave no store (ex: 'tarefa', 'solicitacaoDeServico').
151
+ */
152
+ type ReducerDep = string;
153
+ /**
154
+ * Manifesto que cada remote exporta para o Core
155
+ * montar menu e rotas dinamicamente.
156
+ */
157
+ interface RemoteMenuItem {
158
+ /** Label exibido no menu */
159
+ label: string;
160
+ /** Rota no react-router (ex: '/manutencao/ordens-de-servico') */
161
+ path: string;
162
+ /** Nome do módulo no exposes do webpack (ex: './OrdensDeServico') */
163
+ module: string;
164
+ /** Contexto para ReducersBundle (ex: 'ordemDeServico') */
165
+ context: string;
166
+ /** Reducer keys que esta tela precisa no store do core */
167
+ reducers?: ReducerDep[];
168
+ /** ID do componente para withPermission */
169
+ componentId?: string;
170
+ /** Ícone react-icons (ex: 'FaTools') */
171
+ icon?: string;
172
+ /**
173
+ * Ordem dentro da sub-section (quando mesma `section.label` é declarada
174
+ * por múltiplos remotes e items são merged). Default = 999.
175
+ */
176
+ order?: number;
177
+ }
178
+ interface RemoteMenuSection {
179
+ /** Nome da seção no menu (ex: 'Manutenção') */
180
+ label: string;
181
+ /** Ícone da seção */
182
+ icon?: string;
183
+ items: RemoteMenuItem[];
184
+ /**
185
+ * NOVO: agrupamento global na MenuBar (ex: 'SGM').
186
+ * Sections com mesmo `group` (ou cujo manifest tem `menuGroup.name` igual)
187
+ * são fundidas em 1 nó top-level.
188
+ */
189
+ group?: string;
190
+ /** NOVO: ordem dentro do group (ou top-level). Default = 999 (final). */
191
+ order?: number;
192
+ /**
193
+ * NOVO: se true e dentro de group, items vão direto pro group
194
+ * (sem sub-grupo intermediário pela section.label). Default false.
195
+ */
196
+ flatten?: boolean;
197
+ }
198
+ /**
199
+ * Rota de formulário / tela interna que não aparece no menu,
200
+ * mas precisa ser registrada como <Route> no host.
201
+ */
202
+ interface RemoteFormRoute {
203
+ /** Rota no react-router (ex: '/solicitacaoDeServicoForm') */
204
+ path: string;
205
+ /** Nome do módulo no exposes do webpack (ex: './SolicitacaoDeServicoForm') */
206
+ module: string;
207
+ /** Contexto para ReducersBundle e HTTP (ex: 'solicitacaoDeServico') */
208
+ context: string;
209
+ /** Reducer keys que esta tela precisa no store do core */
210
+ reducers?: ReducerDep[];
211
+ }
212
+ /**
213
+ * Reducers que o remote disponibiliza, agrupados por chave do store.
214
+ * O manifest declara quais cada rota precisa; o ReducersBundle provê os importadores.
215
+ */
216
+ interface RemoteReducerMap {
217
+ [storeKey: string]: () => Promise<any>;
218
+ }
219
+ /**
220
+ * Reducers defaults que são injetados em TODAS as rotas do remote,
221
+ * independentemente do que cada rota declara.
222
+ */
223
+ type DefaultReducerKeys = string[];
224
+ interface RemoteManifest {
225
+ /** Nome do remote (deve corresponder ao name do ModuleFederationPlugin) */
226
+ name: string;
227
+ /** Versão semântica */
228
+ version: string;
229
+ /** Seções de menu que este remote contribui */
230
+ menuSections: RemoteMenuSection[];
231
+ /** Rotas internas (formulários, detalhe) que não aparecem no menu */
232
+ formRoutes?: RemoteFormRoute[];
233
+ /**
234
+ * Mapa de TODOS os reducers que este remote disponibiliza.
235
+ * Chave = nome no store, valor = lazy import do reducer.
236
+ * Se presente, o core usa isso + reducers[] de cada rota para injeção granular.
237
+ * Se ausente, fallback para o ReducersBundle legado.
238
+ */
239
+ reducerMap?: RemoteReducerMap;
240
+ /** Reducer keys injetados em TODAS as rotas deste remote */
241
+ defaultReducers?: DefaultReducerKeys;
242
+ /**
243
+ * NOVO opcional: aplica `group=name` a TODAS as `menuSections` deste remote
244
+ * (atalho declarativo). Sections com seu próprio `group` ainda têm precedência.
245
+ */
246
+ menuGroup?: {
247
+ name: string;
248
+ icon?: string;
249
+ order?: number;
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Factories for creating standardized ReducersBundles.
255
+ *
256
+ * Two APIs:
257
+ *
258
+ * 1. `createReducersFromManifest` (RECOMENDADO) — Manifest-driven.
259
+ * O manifest.js é a FONTE ÚNICA DE VERDADE: declara rotas, menu E reducer deps.
260
+ * Zero contextMap manual.
261
+ *
262
+ * ```ts
263
+ * import { createReducersFromManifest } from 'teraprox-core-sdk/federation'
264
+ * import { manifest } from './manifest'
265
+ *
266
+ * export default createReducersFromManifest(manifest, {
267
+ * solicitacaoDeServico: () => import('../Reducers/solicitacaoDeServicoReducer'),
268
+ * tarefa: () => import('../Reducers/tarefaReducer'),
269
+ * justificativa: () => import('../Reducers/justificativaReducer'),
270
+ * globalError: () => import('../Reducers/default-reducers/globalErrorReducer'),
271
+ * })
272
+ * ```
273
+ *
274
+ * 2. `createReducersBundle` (LEGADO) — contextMap manual.
275
+ * Mantido para retrocompatibilidade com remotes que ainda não declararam
276
+ * `reducers[]` nas rotas do manifest.
147
277
  */
278
+
148
279
  interface ReducersBundleConfig {
149
280
  /** Map de nome → importador lazy do reducer */
150
281
  reducers: Record<string, () => Promise<any>>;
@@ -159,10 +290,22 @@ interface ReducersBundle {
159
290
  getReducersForModule(opts: {
160
291
  context?: string;
161
292
  modulePath?: string;
293
+ reducerKeys?: string[];
162
294
  }): Promise<Record<string, any>>;
163
295
  loadAllReducers(): Promise<Record<string, any>>;
164
296
  baseReducers: Record<string, never>;
165
297
  }
298
+ /**
299
+ * Cria um ReducersBundle a partir do manifest + mapa de importadores.
300
+ *
301
+ * O contextMap é DERIVADO automaticamente dos campos `reducers[]` e `context`
302
+ * de cada rota do manifest. Nenhuma declaração manual de contexto é necessária.
303
+ *
304
+ * @param manifest — O mesmo objeto exportado como ./Manifest
305
+ * @param reducerMap — { storeKey: () => import('...') } para TODOS os reducers do remote
306
+ * @param defaultReducerKeys — Keys injetadas em todas as rotas (ex: 'globalError', 'notification')
307
+ */
308
+ declare function createReducersFromManifest(manifest: RemoteManifest, reducerMap: Record<string, () => Promise<any>>, defaultReducerKeys?: string[]): ReducersBundle;
166
309
  declare function createReducersBundle(config: ReducersBundleConfig): ReducersBundle;
167
310
 
168
311
  interface EmulatorConfig {
@@ -172,12 +315,30 @@ interface EmulatorConfig {
172
315
  }
173
316
  interface StandaloneConfig {
174
317
  /**
175
- * Factory for creating an HttpController. The remote provides its
176
- * local implementation (using basicController + axios).
318
+ * Implementação de `CoreService.createController` **somente para modo standalone**
319
+ * (npm start do remote sem o shell). É a mesma *assinatura* que o core expõe
320
+ * em `CoreServiceProvider`, mas o host injeta a dele via `FederatedBridge`;
321
+ * telas devem sempre usar `useCoreService().createController` — nunca chamar
322
+ * esta factory diretamente.
177
323
  */
178
324
  createController: (context: string, baseEndPoint?: string) => HttpController;
179
- /** Toast function from react-toast-notifications */
180
- addToast: (message: string, options?: any) => void;
325
+ /**
326
+ * Pass a ToastService implementation directly (preferred).
327
+ * Eliminates the need for an addToast bridge in every remote.
328
+ *
329
+ * Example:
330
+ * ```
331
+ * const myToast: ToastService = { success: ..., warning: ..., error: ..., info: ... }
332
+ * <StandaloneProvider toast={myToast} ...>
333
+ * ```
334
+ */
335
+ toast?: ToastService;
336
+ /**
337
+ * react-toast-notifications style callback — kept for backward compatibility
338
+ * with remotes that already use ToastProvider + useToasts().
339
+ * When `toast` prop is provided, `addToast` is ignored.
340
+ */
341
+ addToast?: (message: string, options?: any) => void;
181
342
  /**
182
343
  * Firebase client config object (from REACT_APP_FIREBASE_CONFIG).
183
344
  * When provided together with `tenant`, enables real-time matching
@@ -201,6 +362,13 @@ interface StandaloneConfig {
201
362
  /**
202
363
  * Tenant / company ID used to build the RTDB path:
203
364
  * `{tenant}/matchingObjects`
365
+ *
366
+ * Resolution order (first non-empty value wins):
367
+ * 1. This prop (dynamic — typically from Redux state after login)
368
+ * 2. `REACT_APP_RTDB_TENANT` env var (static — set in the app's .env)
369
+ * 3. `'dev-local'` as last resort in NODE_ENV=development
370
+ *
371
+ * In production the prop MUST be provided; env-var fallback is for dev only.
204
372
  */
205
373
  tenant?: string;
206
374
  children: React__default.ReactNode;
@@ -238,7 +406,7 @@ interface StandaloneConfig {
238
406
  * }
239
407
  * ```
240
408
  */
241
- declare function StandaloneProvider({ createController, addToast, firebaseConfig, emulator, tenant, children }: StandaloneConfig): react_jsx_runtime.JSX.Element;
409
+ declare function StandaloneProvider({ createController, toast: toastProp, addToast, firebaseConfig, emulator, tenant, children, }: StandaloneConfig): react_jsx_runtime.JSX.Element;
242
410
 
243
411
  interface DevUser {
244
412
  firstName: string;
@@ -283,37 +451,51 @@ interface DevAutoLoginProps {
283
451
  declare function DevAutoLogin({ actions, devUser, children }: DevAutoLoginProps): react_jsx_runtime.JSX.Element;
284
452
 
285
453
  /**
286
- * Manifesto que cada remote exporta para o Core
287
- * montar menu e rotas dinamicamente.
454
+ * top-level da árvore de menu agregada.
455
+ * Pode representar um `group` (vários remotes/sections fundidos)
456
+ * ou uma section sem group (legacy / retrocompat).
288
457
  */
289
- interface RemoteMenuItem {
290
- /** Label exibido no menu */
458
+ interface MenuTreeNode {
459
+ /** Identificador único `group:<name>` ou `<remote>:<sectionLabel>` */
460
+ key: string;
461
+ /** Label exibido no dropdown principal */
291
462
  label: string;
292
- /** Rota no react-router (ex: '/manutencao/ordens-de-servico') */
293
- path: string;
294
- /** Nome do módulo no exposes do webpack (ex: './OrdensDeServico') */
295
- module: string;
296
- /** Contexto para ReducersBundle (ex: 'ordemDeServico') */
297
- context: string;
298
- /** ID do componente para withPermission */
299
- componentId?: string;
300
- /** Ícone react-icons (ex: 'FaTools') */
463
+ /** Ícone (react-icons name) */
301
464
  icon?: string;
465
+ /** Order (default 999) — usado para sort estável */
466
+ order: number;
467
+ /** Filhos: sub-sections (quando agrupando) ou items diretos */
468
+ children: MenuTreeChild[];
302
469
  }
303
- interface RemoteMenuSection {
304
- /** Nome da seção no menu (ex: 'Manutenção') */
470
+ /**
471
+ * Filho de um MenuTreeNode.
472
+ * - kind='section': sub-cabeçalho com items aninhados (default p/ group)
473
+ * - kind='item': item direto (top-level legacy ou flatten=true)
474
+ */
475
+ type MenuTreeChild = {
476
+ kind: 'section';
305
477
  label: string;
306
- /** Ícone da seção */
307
478
  icon?: string;
308
479
  items: RemoteMenuItem[];
309
- }
310
- interface RemoteManifest {
311
- /** Nome do remote (deve corresponder ao name do ModuleFederationPlugin) */
312
- name: string;
313
- /** Versão semântica */
314
- version: string;
315
- /** Seções de menu que este remote contribui */
316
- menuSections: RemoteMenuSection[];
317
- }
480
+ order?: number;
481
+ } | {
482
+ kind: 'item';
483
+ item: RemoteMenuItem;
484
+ order?: number;
485
+ };
486
+ /**
487
+ * Recebe array de manifests e produz árvore agregada por `group`.
488
+ *
489
+ * Regras:
490
+ * - Sections sem group e sem `manifest.menuGroup` → top-level node próprio
491
+ * (children = items diretos kind='item' — comportamento legado)
492
+ * - Sections com group (próprio ou herdado de menuGroup) → fundidas sob 1 node
493
+ * `{ key='group:<name>', label=<name> }`
494
+ * - flatten=true: items entram direto como children kind='item'
495
+ * - flatten=false (default): wrapping `{kind:'section', label, items}`
496
+ * - `menuGroup` do manifest aplica fallback para sections que não declaram
497
+ * o próprio `group` (não sobrescreve override por section).
498
+ */
499
+ declare function groupMenuSections(manifests: RemoteManifest[]): MenuTreeNode[];
318
500
 
319
- export { type BreadcrumbPayload as B, type CoreService as C, DevAutoLogin as D, FederatedBridge as F, type HttpController as H, type IObservabilityPort as I, type MatchingObjectSubscription as M, type ReducersBundle as R, StandaloneProvider as S, type ToastService as T, type VitalsPayload as V, type InteractionPayload as a, type ReducersBundleConfig as b, type RemoteManifest as c, type RemoteMenuItem as d, type RemoteMenuSection as e, type ToastOptions as f, createReducersBundle as g };
501
+ export { type BreadcrumbPayload as B, type CoreService as C, type DefaultReducerKeys as D, FederatedBridge as F, type HttpController as H, type IObservabilityPort as I, type MatchingObjectSubscription as M, type RateLimitEntry as R, StandaloneProvider as S, type ToastService as T, type VitalsPayload as V, type InteractionPayload as a, DevAutoLogin as b, type MenuTreeChild as c, type MenuTreeNode as d, type ReducerDep as e, type ReducersBundle as f, type ReducersBundleConfig as g, type RemoteFormRoute as h, type RemoteManifest as i, type RemoteMenuItem as j, type RemoteMenuSection as k, type RemoteReducerMap as l, type ToastOptions as m, createReducersBundle as n, createReducersFromManifest as o, groupMenuSections as p, isHostedByCore as q };
@@ -1,3 +1,3 @@
1
- export { D as DevAutoLogin, F as FederatedBridge, R as ReducersBundle, b as ReducersBundleConfig, c as RemoteManifest, d as RemoteMenuItem, e as RemoteMenuSection, S as StandaloneProvider, g as createReducersBundle } from './federation-Bhx0XhSP.mjs';
1
+ export { D as DefaultReducerKeys, b as DevAutoLogin, F as FederatedBridge, c as MenuTreeChild, d as MenuTreeNode, e as ReducerDep, f as ReducersBundle, g as ReducersBundleConfig, h as RemoteFormRoute, i as RemoteManifest, j as RemoteMenuItem, k as RemoteMenuSection, l as RemoteReducerMap, S as StandaloneProvider, n as createReducersBundle, o as createReducersFromManifest, p as groupMenuSections, q as isHostedByCore } from './federation-Cp_lk0Xd.mjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { D as DevAutoLogin, F as FederatedBridge, R as ReducersBundle, b as ReducersBundleConfig, c as RemoteManifest, d as RemoteMenuItem, e as RemoteMenuSection, S as StandaloneProvider, g as createReducersBundle } from './federation-Bhx0XhSP.js';
1
+ export { D as DefaultReducerKeys, b as DevAutoLogin, F as FederatedBridge, c as MenuTreeChild, d as MenuTreeNode, e as ReducerDep, f as ReducersBundle, g as ReducersBundleConfig, h as RemoteFormRoute, i as RemoteManifest, j as RemoteMenuItem, k as RemoteMenuSection, l as RemoteReducerMap, S as StandaloneProvider, n as createReducersBundle, o as createReducersFromManifest, p as groupMenuSections, q as isHostedByCore } from './federation-Cp_lk0Xd.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -33,7 +33,10 @@ __export(federation_exports, {
33
33
  DevAutoLogin: () => DevAutoLogin,
34
34
  FederatedBridge: () => FederatedBridge,
35
35
  StandaloneProvider: () => StandaloneProvider,
36
- createReducersBundle: () => createReducersBundle
36
+ createReducersBundle: () => createReducersBundle,
37
+ createReducersFromManifest: () => createReducersFromManifest,
38
+ groupMenuSections: () => groupMenuSections,
39
+ isHostedByCore: () => isHostedByCore
37
40
  });
38
41
  module.exports = __toCommonJS(federation_exports);
39
42
 
@@ -58,25 +61,78 @@ function FederatedBridge({ coreService, children }) {
58
61
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CoreServiceContext.Provider, { value: coreService, children });
59
62
  }
60
63
 
64
+ // src/federation/isHostedByCore.ts
65
+ function isHostedByCore() {
66
+ if (typeof window === "undefined") return false;
67
+ return !!window.__TERAPROX_HOSTED_BY_CORE__;
68
+ }
69
+
61
70
  // src/federation/createReducersBundle.ts
71
+ function buildGetReducersForKeys(reducers) {
72
+ return async (keys = []) => {
73
+ const uniqueKeys = [...new Set(keys)].filter((key) => !!reducers[key]);
74
+ const loaded = await Promise.all(
75
+ uniqueKeys.map(async (key) => {
76
+ const mod = await reducers[key]();
77
+ return [key, mod.default || mod];
78
+ })
79
+ );
80
+ return Object.fromEntries(loaded);
81
+ };
82
+ }
83
+ function createReducersFromManifest(manifest, reducerMap, defaultReducerKeys = []) {
84
+ var _a, _b, _c, _d, _e, _f;
85
+ const allKeys = Object.keys(reducerMap);
86
+ const getReducersForKeys = buildGetReducersForKeys(reducerMap);
87
+ const allRoutes = [];
88
+ for (const section of (_a = manifest.menuSections) != null ? _a : []) {
89
+ allRoutes.push(...(_b = section.items) != null ? _b : []);
90
+ }
91
+ allRoutes.push(...(_c = manifest.formRoutes) != null ? _c : []);
92
+ const derivedContextMap = {};
93
+ for (const route of allRoutes) {
94
+ if (!route.context) continue;
95
+ const routeReducers = (_d = route.reducers) != null ? _d : [];
96
+ const existing = (_e = derivedContextMap[route.context]) != null ? _e : [];
97
+ derivedContextMap[route.context] = [.../* @__PURE__ */ new Set([...existing, ...routeReducers])];
98
+ }
99
+ const mergedDefaults = [
100
+ .../* @__PURE__ */ new Set([...defaultReducerKeys, ...(_f = manifest.defaultReducers) != null ? _f : []])
101
+ ];
102
+ const getReducerKeysByContext = (context) => {
103
+ const contextKeys = derivedContextMap[context];
104
+ if (!contextKeys || contextKeys.length === 0) return allKeys;
105
+ return [.../* @__PURE__ */ new Set([...mergedDefaults, ...contextKeys])];
106
+ };
107
+ const getReducersForModule = async ({
108
+ context,
109
+ reducerKeys
110
+ } = {}) => {
111
+ if (reducerKeys && reducerKeys.length > 0) {
112
+ const keys2 = [.../* @__PURE__ */ new Set([...mergedDefaults, ...reducerKeys])];
113
+ return getReducersForKeys(keys2);
114
+ }
115
+ const keys = getReducerKeysByContext(context || "");
116
+ return getReducersForKeys(keys);
117
+ };
118
+ const loadAllReducers = () => getReducersForKeys(allKeys);
119
+ return {
120
+ getReducerKeysByContext,
121
+ getReducersForKeys,
122
+ getReducersForModule,
123
+ loadAllReducers,
124
+ baseReducers: {}
125
+ };
126
+ }
62
127
  function createReducersBundle(config) {
63
128
  const { reducers, contextMap, defaults = [] } = config;
64
129
  const allKeys = Object.keys(reducers);
130
+ const getReducersForKeys = buildGetReducersForKeys(reducers);
65
131
  const getReducerKeysByContext = (context) => {
66
132
  const contextKeys = contextMap[context];
67
133
  if (!contextKeys) return allKeys;
68
134
  return [.../* @__PURE__ */ new Set([...defaults, ...contextKeys])];
69
135
  };
70
- const getReducersForKeys = async (keys = []) => {
71
- const uniqueKeys = [...new Set(keys)].filter((key) => !!reducers[key]);
72
- const loaded = await Promise.all(
73
- uniqueKeys.map(async (key) => {
74
- const module2 = await reducers[key]();
75
- return [key, module2.default || module2];
76
- })
77
- );
78
- return Object.fromEntries(loaded);
79
- };
80
136
  const getReducersForModule = async ({
81
137
  context
82
138
  } = {}) => {
@@ -215,20 +271,38 @@ function RtdbConfigWarning({ onDismiss }) {
215
271
  }
216
272
  );
217
273
  }
218
- function StandaloneProvider({ createController, addToast, firebaseConfig, emulator, tenant, children }) {
274
+ function StandaloneProvider({
275
+ createController,
276
+ toast: toastProp,
277
+ addToast,
278
+ firebaseConfig,
279
+ emulator,
280
+ tenant,
281
+ children
282
+ }) {
219
283
  const [subscriptions] = (0, import_react3.useState)([]);
220
284
  const subscriptionsRef = (0, import_react3.useRef)(subscriptions);
221
285
  subscriptionsRef.current = subscriptions;
222
286
  const [rtdbWarning, setRtdbWarning] = (0, import_react3.useState)(false);
223
- const toast = (0, import_react3.useMemo)(
224
- () => ({
225
- success: (msg, opts) => addToast(msg, { appearance: "success", autoDismiss: true, ...opts }),
226
- warning: (msg, opts) => addToast(msg, { appearance: "warning", autoDismiss: true, ...opts }),
227
- error: (msg, opts) => addToast(msg, { appearance: "error", autoDismiss: true, ...opts }),
228
- info: (msg, opts) => addToast(msg, { appearance: "info", autoDismiss: true, ...opts })
229
- }),
230
- [addToast]
231
- );
287
+ const _envTenant = typeof process !== "undefined" ? process.env.REACT_APP_RTDB_TENANT : void 0;
288
+ const resolvedTenant = tenant || _envTenant || (process.env.NODE_ENV !== "production" ? "dev-local" : void 0);
289
+ const toast = (0, import_react3.useMemo)(() => {
290
+ if (toastProp) return toastProp;
291
+ if (addToast) {
292
+ return {
293
+ success: (msg, opts) => addToast(msg, { appearance: "success", autoDismiss: true, ...opts }),
294
+ warning: (msg, opts) => addToast(msg, { appearance: "warning", autoDismiss: true, ...opts }),
295
+ error: (msg, opts) => addToast(msg, { appearance: "error", autoDismiss: true, ...opts }),
296
+ info: (msg, opts) => addToast(msg, { appearance: "info", autoDismiss: true, ...opts })
297
+ };
298
+ }
299
+ const noop = (msg) => {
300
+ if (process.env.NODE_ENV !== "production") {
301
+ console.warn("[StandaloneProvider] Toast called but no toast/addToast prop provided:", msg);
302
+ }
303
+ };
304
+ return { success: noop, warning: noop, error: noop, info: noop };
305
+ }, [toastProp, addToast]);
232
306
  const subscribe = (0, import_react3.useCallback)(
233
307
  (mo) => {
234
308
  subscriptions.push(mo);
@@ -245,7 +319,16 @@ function StandaloneProvider({ createController, addToast, firebaseConfig, emulat
245
319
  [subscriptions]
246
320
  );
247
321
  (0, import_react3.useEffect)(() => {
248
- if (!tenant) return;
322
+ if (resolvedTenant || process.env.NODE_ENV === "production") return;
323
+ const timer = setTimeout(() => {
324
+ console.warn(
325
+ '[StandaloneProvider] RTDB listener deferred: tenant could not be resolved after 5 s.\nFix with any of:\n \u2022 Pass `tenant` prop (e.g. from Redux state.global.companyId)\n \u2022 Set REACT_APP_RTDB_TENANT=<companyId> in your .env\n \u2022 Run in NODE_ENV=development (auto-falls back to "dev-local")'
326
+ );
327
+ }, 5e3);
328
+ return () => clearTimeout(timer);
329
+ }, [resolvedTenant]);
330
+ (0, import_react3.useEffect)(() => {
331
+ if (!resolvedTenant) return;
249
332
  let cleanup;
250
333
  let cancelled = false;
251
334
  (async () => {
@@ -288,7 +371,7 @@ function StandaloneProvider({ createController, addToast, firebaseConfig, emulat
288
371
  const cloudApp = existingApp != null ? existingApp : initializeApp(strategy.config);
289
372
  db = getDatabase(cloudApp);
290
373
  }
291
- const moRef = ref(db, `${tenant}/matchingObjects`);
374
+ const moRef = ref(db, `${resolvedTenant}/matchingObjects`);
292
375
  const unsub = onChildAdded(moRef, (snapshot) => {
293
376
  const data = snapshot.val();
294
377
  if (!data) return;
@@ -318,7 +401,7 @@ function StandaloneProvider({ createController, addToast, firebaseConfig, emulat
318
401
  cancelled = true;
319
402
  cleanup == null ? void 0 : cleanup();
320
403
  };
321
- }, [firebaseConfig, emulator, tenant]);
404
+ }, [firebaseConfig, emulator, resolvedTenant]);
322
405
  const value = (0, import_react3.useMemo)(
323
406
  () => ({
324
407
  createController,
@@ -332,7 +415,8 @@ function StandaloneProvider({ createController, addToast, firebaseConfig, emulat
332
415
  handleLogout: () => {
333
416
  },
334
417
  hostedByCore: false,
335
- observability: new NullObservabilityAdapter()
418
+ observability: new NullObservabilityAdapter(),
419
+ rateLimits: {}
336
420
  }),
337
421
  [createController, toast, subscribe, unsubscribe]
338
422
  );
@@ -377,10 +461,99 @@ function DevAutoLogin({ actions, devUser, children }) {
377
461
  }, [dispatch, hostedByCore, token, actions, devUser]);
378
462
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
379
463
  }
464
+
465
+ // src/federation/groupMenuSections.ts
466
+ function groupMenuSections(manifests) {
467
+ var _a, _b, _c, _d, _e, _f, _g;
468
+ const groupMap = /* @__PURE__ */ new Map();
469
+ const topLevel = [];
470
+ for (const manifest of manifests) {
471
+ const fallbackGroup = manifest.menuGroup;
472
+ for (const section of (_a = manifest.menuSections) != null ? _a : []) {
473
+ const groupName = (_b = section.group) != null ? _b : fallbackGroup == null ? void 0 : fallbackGroup.name;
474
+ if (!groupName) {
475
+ topLevel.push({
476
+ key: `${manifest.name}:${section.label}`,
477
+ label: section.label,
478
+ icon: section.icon,
479
+ order: (_c = section.order) != null ? _c : 999,
480
+ children: section.items.map((item) => ({ kind: "item", item }))
481
+ });
482
+ continue;
483
+ }
484
+ let groupNode = groupMap.get(groupName);
485
+ if (!groupNode) {
486
+ groupNode = {
487
+ key: `group:${groupName}`,
488
+ label: groupName,
489
+ icon: (_d = fallbackGroup == null ? void 0 : fallbackGroup.icon) != null ? _d : section.icon,
490
+ order: (_f = (_e = fallbackGroup == null ? void 0 : fallbackGroup.order) != null ? _e : section.order) != null ? _f : 999,
491
+ children: []
492
+ };
493
+ groupMap.set(groupName, groupNode);
494
+ } else if (!groupNode.icon) {
495
+ const candidate = (_g = fallbackGroup == null ? void 0 : fallbackGroup.icon) != null ? _g : section.icon;
496
+ if (candidate) groupNode.icon = candidate;
497
+ }
498
+ if (section.flatten) {
499
+ for (const item of section.items) {
500
+ groupNode.children.push({ kind: "item", item, order: section.order });
501
+ }
502
+ } else {
503
+ const existing = groupNode.children.find(
504
+ (c) => c.kind === "section" && c.label === section.label
505
+ );
506
+ if (existing) {
507
+ existing.items = [...existing.items, ...section.items];
508
+ if (!existing.icon && section.icon) existing.icon = section.icon;
509
+ if (section.order != null && (existing.order == null || section.order < existing.order)) {
510
+ existing.order = section.order;
511
+ }
512
+ } else {
513
+ groupNode.children.push({
514
+ kind: "section",
515
+ label: section.label,
516
+ icon: section.icon,
517
+ items: [...section.items],
518
+ order: section.order
519
+ });
520
+ }
521
+ }
522
+ }
523
+ }
524
+ for (const node of groupMap.values()) {
525
+ node.children.sort((a, b) => {
526
+ var _a2, _b2, _c2, _d2;
527
+ const oa = (_a2 = a.order) != null ? _a2 : 999;
528
+ const ob = (_b2 = b.order) != null ? _b2 : 999;
529
+ if (oa !== ob) return oa - ob;
530
+ const la = a.kind === "section" ? a.label : (_c2 = a.item.label) != null ? _c2 : "";
531
+ const lb = b.kind === "section" ? b.label : (_d2 = b.item.label) != null ? _d2 : "";
532
+ return la.localeCompare(lb);
533
+ });
534
+ for (const child of node.children) {
535
+ if (child.kind === "section") {
536
+ child.items.sort((a, b) => {
537
+ var _a2, _b2, _c2, _d2;
538
+ const oa = (_a2 = a.order) != null ? _a2 : 999;
539
+ const ob = (_b2 = b.order) != null ? _b2 : 999;
540
+ if (oa !== ob) return oa - ob;
541
+ return ((_c2 = a.label) != null ? _c2 : "").localeCompare((_d2 = b.label) != null ? _d2 : "");
542
+ });
543
+ }
544
+ }
545
+ }
546
+ const all = [...topLevel, ...groupMap.values()];
547
+ all.sort((a, b) => a.order - b.order || a.label.localeCompare(b.label));
548
+ return all;
549
+ }
380
550
  // Annotate the CommonJS export names for ESM import in node:
381
551
  0 && (module.exports = {
382
552
  DevAutoLogin,
383
553
  FederatedBridge,
384
554
  StandaloneProvider,
385
- createReducersBundle
555
+ createReducersBundle,
556
+ createReducersFromManifest,
557
+ groupMenuSections,
558
+ isHostedByCore
386
559
  });