ptech-shell-dev 1.7.0 → 1.8.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { LogLevel, RealtimeEnvelope, UserService, RequestContextService, RealtimeService, Lang, User, I18nService, ApiClient, NavigationService, ConfigService, PermissionService, SharedStateService, ObservabilityService, NotificationService, AnalyticsService, ObservabilityConfigPatch, ApiErrorPayload, ApiValidationErrors, ApiRetryOptions, MsalAccountInfoLike, NavigationAction, NavigationTo, NavigationNavigateOptions } from 'ptech-shell-sdk';
1
+ import { LogLevel, RealtimeEnvelope, UserService, RequestContextService, RealtimeService, Lang, User, I18nService, ApiClient, NavigationService, ConfigService, PermissionService, SharedStateService, ObservabilityService, NotificationService, AnalyticsService, TenantService, AppSettingsService, ObservabilityConfigPatch, AppSettings, ApiErrorPayload, ApiValidationErrors, ApiRetryOptions, MsalAccountInfoLike, NavigationAction, NavigationTo, NavigationNavigateOptions } from 'ptech-shell-sdk';
2
2
 
3
3
  type ObservabilityTelemetryEvent = {
4
4
  level: LogLevel;
@@ -74,6 +74,8 @@ type StandaloneServices = {
74
74
  observability: ObservabilityService;
75
75
  notification: NotificationService;
76
76
  analytics: AnalyticsService;
77
+ tenantService: TenantService;
78
+ appSettingsService: AppSettingsService;
77
79
  };
78
80
  type ServiceRegisterMode = 'if-missing' | 'always';
79
81
  type StandaloneInitOptions = {
@@ -135,6 +137,23 @@ type StandaloneInitOptions = {
135
137
  envName?: string;
136
138
  apiBase?: string;
137
139
  };
140
+ /**
141
+ * Seed values for the standalone tenant service.
142
+ * Defaults to `{ tenantId: 'dev-tenant', tenantSlug: 'dev' }`.
143
+ */
144
+ tenant?: {
145
+ tenantId?: string | null;
146
+ tenantSlug?: string | null;
147
+ };
148
+ /**
149
+ * Seed/loader for the standalone app settings service.
150
+ * - `seed`: static per-appKey map returned by `load()`.
151
+ * - `fetcher`: override that takes precedence over seed.
152
+ */
153
+ appSettings?: {
154
+ seed?: Readonly<Record<string, AppSettings>>;
155
+ fetcher?: (appKey: string) => Promise<AppSettings>;
156
+ };
138
157
  };
139
158
 
140
159
  /**
@@ -182,6 +201,12 @@ type CreateStandaloneApiClientOptions = {
182
201
  */
183
202
  getRequestContext?: () => RequestContextService | undefined;
184
203
  requestContext?: RequestContextService;
204
+ /**
205
+ * Optional dynamic lookup for tenant service; header `X-Tenant-Id` is injected
206
+ * when the resolved snapshot has a non-empty `tenantId`.
207
+ */
208
+ getTenantService?: () => TenantService | undefined;
209
+ tenantService?: TenantService;
185
210
  defaultAuthScopes?: string[];
186
211
  defaultTimeoutMs?: number;
187
212
  defaultRetries?: ApiRetryOptions | false;
@@ -233,6 +258,32 @@ declare function isApiError(value: unknown): value is ApiError;
233
258
  */
234
259
  declare function createStandaloneApiClient(options: string | CreateStandaloneApiClientOptions): ApiClient;
235
260
 
261
+ type CreateStandaloneAppSettingsServiceOptions = {
262
+ /**
263
+ * Static seed settings keyed by appKey.
264
+ * Returned instantly from `load()` and `get()` for dev/test scenarios.
265
+ */
266
+ seed?: Readonly<Record<string, AppSettings>>;
267
+ /**
268
+ * Optional async loader, overrides seed when provided.
269
+ * Useful when tests want to simulate latency or network failures.
270
+ */
271
+ fetcher?: (appKey: string) => Promise<AppSettings>;
272
+ };
273
+ /**
274
+ * WHY:
275
+ * - In-memory per-appKey settings store for standalone remote development.
276
+ * WHEN TO USE:
277
+ * - Use via initStandaloneServices so remotes can call `load(appKey)` without a host.
278
+ * WHEN NOT TO USE:
279
+ * - Do not use in production — host fetches from backend and owns cache invalidation.
280
+ * INVARIANTS:
281
+ * - `get()` returns null until `load()` resolves for that appKey.
282
+ * - `invalidate()` with no args drops all cached entries.
283
+ * - Listeners are scoped per appKey; invalidate emits for affected keys only.
284
+ */
285
+ declare function createStandaloneAppSettingsService(options?: CreateStandaloneAppSettingsServiceOptions): AppSettingsService;
286
+
236
287
  type UiApiError = {
237
288
  i18nKey: string;
238
289
  params: Record<string, string | number>;
@@ -401,6 +452,23 @@ type CreateRequestContextServiceOptions = {
401
452
  */
402
453
  declare function createRequestContextService(options?: CreateRequestContextServiceOptions): RequestContextService;
403
454
 
455
+ type CreateStandaloneTenantServiceOptions = {
456
+ tenantId?: string | null;
457
+ tenantSlug?: string | null;
458
+ };
459
+ /**
460
+ * WHY:
461
+ * - Provides a minimal in-memory TenantService for standalone remote dev.
462
+ * WHEN TO USE:
463
+ * - Use via initStandaloneServices when a remote runs without host.
464
+ * WHEN NOT TO USE:
465
+ * - Do not use in production — host registers its own tenant service.
466
+ * INVARIANTS:
467
+ * - getSnapshot returns a copy, never the mutable internal reference.
468
+ * - setTenant emits only when either field actually changes.
469
+ */
470
+ declare function createStandaloneTenantService(options?: CreateStandaloneTenantServiceOptions): TenantService;
471
+
404
472
  /**
405
473
  * WHY:
406
474
  * - Provides mock user/session/auth token behavior for standalone shell development.
@@ -415,4 +483,4 @@ declare function createRequestContextService(options?: CreateRequestContextServi
415
483
  */
416
484
  declare function createStandaloneUserService(seedUser: User | null): UserService;
417
485
 
418
- export { ApiError, type CreateMsalUserServiceOptions, type CreateReactRouterNavigationAdapterOptions, type CreateStandaloneApiClientOptions, type CreateStandaloneObservabilityServiceOptions, type CreateStandaloneRealtimeServiceOptions, type MsalBrowserInstanceLike, type ObservabilityTelemetryEvent, type ObservabilityTelemetrySink, type ReactRouterCreateHrefFunctionLike, type ReactRouterLocationLike, type ReactRouterNavigateFunctionLike, type ReactRouterNavigationAdapter, type ReactRouterNavigationCapabilities, type RealtimeReconnectOptions, type RealtimeTransport, type RealtimeTransportConnectContext, type ServiceRegisterMode, type StandaloneInitOptions, type StandaloneServices, type UiApiError, createDevPreset, createMsalUserService, createReactRouterNavigationAdapter, createRequestContextService, createStandaloneApiClient, createStandaloneI18nService, createStandaloneRealtimeService, createStandaloneUserService, createTestServices, initStandaloneServices, isApiError, mapApiErrorToUiError };
486
+ export { ApiError, type CreateMsalUserServiceOptions, type CreateReactRouterNavigationAdapterOptions, type CreateStandaloneApiClientOptions, type CreateStandaloneObservabilityServiceOptions, type CreateStandaloneRealtimeServiceOptions, type MsalBrowserInstanceLike, type ObservabilityTelemetryEvent, type ObservabilityTelemetrySink, type ReactRouterCreateHrefFunctionLike, type ReactRouterLocationLike, type ReactRouterNavigateFunctionLike, type ReactRouterNavigationAdapter, type ReactRouterNavigationCapabilities, type RealtimeReconnectOptions, type RealtimeTransport, type RealtimeTransportConnectContext, type ServiceRegisterMode, type StandaloneInitOptions, type StandaloneServices, type UiApiError, createDevPreset, createMsalUserService, createReactRouterNavigationAdapter, createRequestContextService, createStandaloneApiClient, createStandaloneAppSettingsService, createStandaloneI18nService, createStandaloneRealtimeService, createStandaloneTenantService, createStandaloneUserService, createTestServices, initStandaloneServices, isApiError, mapApiErrorToUiError };
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ var DEFAULT_RETRY_OPTIONS = {
66
66
  var TRACE_HEADER = "traceparent";
67
67
  var CORRELATION_HEADER = "x-correlation-id";
68
68
  var CORRELATION_HEADERS = ["x-correlation-id", "x-request-id"];
69
+ var TENANT_HEADER = "x-tenant-id";
69
70
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
70
71
  function isApiError(value) {
71
72
  return value instanceof ApiError;
@@ -418,6 +419,7 @@ function resolveCreateOptions(input) {
418
419
  getUserService: input.getUserService ?? (input.userService ? () => input.userService : void 0),
419
420
  getNotificationService: input.getNotificationService ?? (input.notificationService ? () => input.notificationService : void 0),
420
421
  getRequestContext: input.getRequestContext ?? (input.requestContext ? () => input.requestContext : void 0),
422
+ getTenantService: input.getTenantService ?? (input.tenantService ? () => input.tenantService : void 0),
421
423
  defaultAuthScopes: input.defaultAuthScopes ?? [],
422
424
  defaultTimeoutMs: input.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS,
423
425
  defaultRetries: input.defaultRetries ?? DEFAULT_RETRY_OPTIONS
@@ -439,6 +441,19 @@ function applyRequestContextHeaders(headers, options) {
439
441
  headers.set(CORRELATION_HEADER, context.correlationId);
440
442
  }
441
443
  }
444
+ function applyTenantHeader(headers, options) {
445
+ if (headers.has(TENANT_HEADER)) {
446
+ return;
447
+ }
448
+ const tenantService = options.getTenantService?.();
449
+ if (!tenantService) {
450
+ return;
451
+ }
452
+ const tenantId = tenantService.getSnapshot().tenantId;
453
+ if (tenantId && tenantId.length > 0) {
454
+ headers.set(TENANT_HEADER, tenantId);
455
+ }
456
+ }
442
457
  function syncResponseContext(response, options) {
443
458
  const contextService = options.getRequestContext?.();
444
459
  if (!contextService) {
@@ -531,6 +546,7 @@ async function executeRaw(requestOptions, options) {
531
546
  const timeoutMs = requestOptions.timeoutMs ?? options.defaultTimeoutMs;
532
547
  const headers = new Headers(requestOptions.headers);
533
548
  applyRequestContextHeaders(headers, options);
549
+ applyTenantHeader(headers, options);
534
550
  await applyAuthorization(headers, requestOptions, options);
535
551
  const url = resolveUrl(options.apiBase, requestOptions.url);
536
552
  const body = resolveBody(requestOptions.body, headers);
@@ -611,6 +627,7 @@ function createStandaloneApiClient(options) {
611
627
  fetch: async (input, init) => {
612
628
  const headers = new Headers(init?.headers);
613
629
  applyRequestContextHeaders(headers, resolvedOptions);
630
+ applyTenantHeader(headers, resolvedOptions);
614
631
  await applyAuthorization(
615
632
  headers,
616
633
  {
@@ -645,6 +662,69 @@ function createStandaloneApiClient(options) {
645
662
  };
646
663
  }
647
664
 
665
+ // src/services/appSettings.ts
666
+ function createStandaloneAppSettingsService(options = {}) {
667
+ const cache = /* @__PURE__ */ new Map();
668
+ const listeners = /* @__PURE__ */ new Map();
669
+ function emit(appKey) {
670
+ const group = listeners.get(appKey);
671
+ if (!group) return;
672
+ for (const listener of group) {
673
+ listener();
674
+ }
675
+ }
676
+ async function resolveSettings(appKey) {
677
+ if (options.fetcher) {
678
+ return options.fetcher(appKey);
679
+ }
680
+ return options.seed?.[appKey] ?? {};
681
+ }
682
+ return {
683
+ get: (appKey) => cache.get(appKey) ?? null,
684
+ load: async (appKey) => {
685
+ const existing = cache.get(appKey);
686
+ if (existing) {
687
+ return existing;
688
+ }
689
+ const settings = await resolveSettings(appKey);
690
+ const snapshot = {
691
+ settings,
692
+ version: 1
693
+ };
694
+ cache.set(appKey, snapshot);
695
+ emit(appKey);
696
+ return snapshot;
697
+ },
698
+ subscribe: (appKey, listener) => {
699
+ let group = listeners.get(appKey);
700
+ if (!group) {
701
+ group = /* @__PURE__ */ new Set();
702
+ listeners.set(appKey, group);
703
+ }
704
+ group.add(listener);
705
+ return () => {
706
+ group?.delete(listener);
707
+ if (group && group.size === 0) {
708
+ listeners.delete(appKey);
709
+ }
710
+ };
711
+ },
712
+ invalidate: (appKey) => {
713
+ if (appKey === void 0) {
714
+ const affected = Array.from(cache.keys());
715
+ cache.clear();
716
+ for (const key of affected) {
717
+ emit(key);
718
+ }
719
+ return;
720
+ }
721
+ if (cache.delete(appKey)) {
722
+ emit(appKey);
723
+ }
724
+ }
725
+ };
726
+ }
727
+
648
728
  // src/services/config.ts
649
729
  import { FEATURE_FLAGS } from "ptech-shell-sdk";
650
730
  var DEFAULT_FLAGS = {
@@ -1530,6 +1610,37 @@ function createStandaloneSharedStateService() {
1530
1610
  };
1531
1611
  }
1532
1612
 
1613
+ // src/services/tenant.ts
1614
+ function createStandaloneTenantService(options = {}) {
1615
+ const listeners = /* @__PURE__ */ new Set();
1616
+ let snapshot = {
1617
+ tenantId: options.tenantId ?? "dev-tenant",
1618
+ tenantSlug: options.tenantSlug ?? "dev"
1619
+ };
1620
+ function emit() {
1621
+ for (const listener of listeners) {
1622
+ listener();
1623
+ }
1624
+ }
1625
+ return {
1626
+ getSnapshot: () => ({ ...snapshot }),
1627
+ setTenant: (tenantId, tenantSlug) => {
1628
+ if (tenantId === snapshot.tenantId && tenantSlug === snapshot.tenantSlug) {
1629
+ return { ...snapshot };
1630
+ }
1631
+ snapshot = { tenantId, tenantSlug };
1632
+ emit();
1633
+ return { ...snapshot };
1634
+ },
1635
+ subscribe: (listener) => {
1636
+ listeners.add(listener);
1637
+ return () => {
1638
+ listeners.delete(listener);
1639
+ };
1640
+ }
1641
+ };
1642
+ }
1643
+
1533
1644
  // src/services/user.ts
1534
1645
  function createStandaloneUserService(seedUser) {
1535
1646
  const listeners = /* @__PURE__ */ new Set();
@@ -1604,11 +1715,15 @@ function createTestServices(options = {}) {
1604
1715
  realtime: realtimeOptions,
1605
1716
  navigationPath = "/standalone",
1606
1717
  featureFlags = {},
1607
- runtimeConfig = {}
1718
+ runtimeConfig = {},
1719
+ tenant,
1720
+ appSettings
1608
1721
  } = options;
1609
1722
  const userService = createStandaloneUserService(user ?? null);
1610
1723
  const notificationService = createStandaloneNotificationService();
1611
1724
  const requestContext = createRequestContextService();
1725
+ const tenantService = createStandaloneTenantService(tenant);
1726
+ const appSettingsService = createStandaloneAppSettingsService(appSettings);
1612
1727
  return {
1613
1728
  i18n: createStandaloneI18nService(lang),
1614
1729
  userService,
@@ -1616,7 +1731,8 @@ function createTestServices(options = {}) {
1616
1731
  apiBase,
1617
1732
  getUserService: () => getService(TOKENS.userService) ?? userService,
1618
1733
  notificationService,
1619
- requestContext
1734
+ requestContext,
1735
+ tenantService
1620
1736
  }),
1621
1737
  navigation: createStandaloneNavigationService(navigationPath),
1622
1738
  configService: createStandaloneConfigService(
@@ -1636,7 +1752,9 @@ function createTestServices(options = {}) {
1636
1752
  getTraceContext: () => requestContext.getSnapshot()
1637
1753
  }),
1638
1754
  notification: notificationService,
1639
- analytics: createStandaloneAnalyticsService()
1755
+ analytics: createStandaloneAnalyticsService(),
1756
+ tenantService,
1757
+ appSettingsService
1640
1758
  };
1641
1759
  }
1642
1760
  function initStandaloneServices(options = {}) {
@@ -1665,7 +1783,9 @@ function initStandaloneServices(options = {}) {
1665
1783
  [TOKENS.realtime, services.realtime],
1666
1784
  [TOKENS.observability, services.observability],
1667
1785
  [TOKENS.notification, services.notification],
1668
- [TOKENS.analytics, services.analytics]
1786
+ [TOKENS.analytics, services.analytics],
1787
+ [TOKENS.tenantService, services.tenantService],
1788
+ [TOKENS.appSettingsService, services.appSettingsService]
1669
1789
  ];
1670
1790
  for (const [token, service] of tokenServiceMap) {
1671
1791
  if (registerMode === "always" || !getService(token)) {
@@ -2167,8 +2287,10 @@ export {
2167
2287
  createReactRouterNavigationAdapter,
2168
2288
  createRequestContextService,
2169
2289
  createStandaloneApiClient,
2290
+ createStandaloneAppSettingsService,
2170
2291
  createStandaloneI18nService,
2171
2292
  createStandaloneRealtimeService,
2293
+ createStandaloneTenantService,
2172
2294
  createStandaloneUserService,
2173
2295
  createTestServices,
2174
2296
  initStandaloneServices,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ptech-shell-dev",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Standalone/mock shell service implementations for Module Federation apps.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,17 +23,17 @@
23
23
  "prepublishOnly": "npm run build"
24
24
  },
25
25
  "dependencies": {
26
- "ptech-shell-sdk": "^1.7.0"
26
+ "ptech-shell-sdk": "^1.8.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">=18"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "^19.2.13",
33
+ "react": "^19.2.4",
34
+ "tsup": "^8.5.1",
35
+ "typescript": "^5.9.3"
27
36
  },
28
- "peerDependencies": {
29
- "react": ">=18"
30
- },
31
- "devDependencies": {
32
- "@types/react": "^19.2.13",
33
- "react": "^19.2.4",
34
- "tsup": "^8.5.1",
35
- "typescript": "^5.9.3"
36
- },
37
37
  "keywords": [
38
38
  "micro-frontend",
39
39
  "module-federation",