@wordrhyme/plugin 0.1.0-alpha.6 → 0.1.0-alpha.7

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.
Files changed (36) hide show
  1. package/dist/artifact.d.ts +1 -1
  2. package/dist/artifact.js +2 -1
  3. package/dist/artifact.js.map +1 -1
  4. package/dist/chunk-BI7E5CVM.js +26 -0
  5. package/dist/chunk-BI7E5CVM.js.map +1 -0
  6. package/dist/{chunk-46KUIGB6.js → chunk-BVOTSKM2.js} +36 -12
  7. package/dist/chunk-BVOTSKM2.js.map +1 -0
  8. package/dist/{chunk-T7PQB64I.js → chunk-FOCLZY64.js} +69 -3
  9. package/dist/chunk-FOCLZY64.js.map +1 -0
  10. package/dist/{chunk-YQDKOEUI.js → chunk-NAWUXVZQ.js} +43 -15
  11. package/dist/chunk-NAWUXVZQ.js.map +1 -0
  12. package/dist/{client-BJju2aT7.d.ts → client-CKtDWayV.d.ts} +1 -1
  13. package/dist/client.d.ts +2 -2
  14. package/dist/client.js +2 -1
  15. package/dist/dev-utils.d.ts +6 -2
  16. package/dist/dev-utils.js +3 -1
  17. package/dist/{entity-extensions-D09rvNgi.d.ts → entity-extensions-gJuSTXYZ.d.ts} +2 -2
  18. package/dist/globalization.d.ts +7 -2
  19. package/dist/globalization.js +11 -1
  20. package/dist/index.d.ts +42 -8
  21. package/dist/index.js +80 -18
  22. package/dist/index.js.map +1 -1
  23. package/dist/locale.d.ts +15 -0
  24. package/dist/locale.js +13 -0
  25. package/dist/locale.js.map +1 -0
  26. package/dist/{manifest-zSGjFCcJ.d.ts → manifest-CDTCsMYV.d.ts} +22 -4
  27. package/dist/react.d.ts +50 -3
  28. package/dist/react.js +105 -11
  29. package/dist/react.js.map +1 -1
  30. package/dist/server.d.ts +5 -4
  31. package/dist/trpc.d.ts +2 -1
  32. package/dist/{types-oPAs313y.d.ts → types-9v-WZ_NU.d.ts} +9 -0
  33. package/package.json +10 -1
  34. package/dist/chunk-46KUIGB6.js.map +0 -1
  35. package/dist/chunk-T7PQB64I.js.map +0 -1
  36. package/dist/chunk-YQDKOEUI.js.map +0 -1
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared locale contract for every WordRhyme runtime surface.
3
+ *
4
+ * Locale parsing delegates to the platform Intl implementation so every
5
+ * caller accepts the same BCP 47 grammar and produces the same casing.
6
+ */
7
+ declare const DEFAULT_LOCALE = "zh-CN";
8
+ /** Return the canonical BCP 47 representation of a locale value. */
9
+ declare function canonicalizeLocale(value: unknown): string | undefined;
10
+ /** Check whether a value is a valid BCP 47 locale. */
11
+ declare function isLocale(value: unknown): value is string;
12
+ /** Canonicalize a locale or use the shared system fallback. */
13
+ declare function normalizeLocale(value: unknown, fallback?: string): string;
14
+
15
+ export { DEFAULT_LOCALE, canonicalizeLocale, isLocale, normalizeLocale };
package/dist/locale.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ DEFAULT_LOCALE,
3
+ canonicalizeLocale,
4
+ isLocale,
5
+ normalizeLocale
6
+ } from "./chunk-BI7E5CVM.js";
7
+ export {
8
+ DEFAULT_LOCALE,
9
+ canonicalizeLocale,
10
+ isLocale,
11
+ normalizeLocale
12
+ };
13
+ //# sourceMappingURL=locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -233,7 +233,16 @@ declare const adminTourSchema: z.ZodObject<{
233
233
  }, z.core.$strip>>;
234
234
  }, z.core.$strip>;
235
235
  type AdminTour = z.infer<typeof adminTourSchema>;
236
- declare const pluginI18nSchema: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>;
236
+ declare const pluginI18nSchema: z.ZodUnion<readonly [z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>, z.ZodTransform<Record<string, Record<string, string>>, Record<string, Record<string, string>>>>, z.ZodObject<{
237
+ namespace: z.ZodOptional<z.ZodString>;
238
+ messages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<Record<string, string>, Record<string, string>>>>>;
239
+ localesFile: z.ZodOptional<z.ZodString>;
240
+ onUninstall: z.ZodOptional<z.ZodEnum<{
241
+ delete: "delete";
242
+ archive: "archive";
243
+ retain: "retain";
244
+ }>>;
245
+ }, z.core.$strict>]>;
237
246
  declare const entityExtensionFieldTypeSchema: z.ZodEnum<{
238
247
  number: "number";
239
248
  boolean: "boolean";
@@ -977,7 +986,16 @@ declare const pluginManifestSchema: z.ZodObject<{
977
986
  integration: "integration";
978
987
  feature: "feature";
979
988
  }>>;
980
- i18n: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
989
+ i18n: z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>, z.ZodTransform<Record<string, Record<string, string>>, Record<string, Record<string, string>>>>, z.ZodObject<{
990
+ namespace: z.ZodOptional<z.ZodString>;
991
+ messages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<Record<string, string>, Record<string, string>>>>>;
992
+ localesFile: z.ZodOptional<z.ZodString>;
993
+ onUninstall: z.ZodOptional<z.ZodEnum<{
994
+ delete: "delete";
995
+ archive: "archive";
996
+ retain: "retain";
997
+ }>>;
998
+ }, z.core.$strict>]>>;
981
999
  runtime: z.ZodDefault<z.ZodEnum<{
982
1000
  node: "node";
983
1001
  }>>;
@@ -1074,10 +1092,10 @@ declare const pluginManifestSchema: z.ZodObject<{
1074
1092
  paths: z.ZodArray<z.ZodString>;
1075
1093
  }, z.core.$strip>>;
1076
1094
  hooks: z.ZodOptional<z.ZodArray<z.ZodEnum<{
1095
+ onUninstall: "onUninstall";
1077
1096
  onInstall: "onInstall";
1078
1097
  onEnable: "onEnable";
1079
1098
  onDisable: "onDisable";
1080
- onUninstall: "onUninstall";
1081
1099
  }>>>;
1082
1100
  }, z.core.$strip>>;
1083
1101
  admin: z.ZodOptional<z.ZodObject<{
@@ -1396,9 +1414,9 @@ declare const pluginManifestSchema: z.ZodObject<{
1396
1414
  retain: "retain";
1397
1415
  }>>;
1398
1416
  onUninstall: z.ZodOptional<z.ZodEnum<{
1399
- retain: "retain";
1400
1417
  delete: "delete";
1401
1418
  archive: "archive";
1419
+ retain: "retain";
1402
1420
  }>>;
1403
1421
  tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
1404
1422
  }, z.core.$strip>>;
package/dist/react.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
- import { P as PluginMediaInfo } from './types-oPAs313y.js';
3
- import './globalization.js';
2
+ import { CurrencyInfo, FormatPriceOptions } from './globalization.js';
3
+ import { P as PluginMediaInfo } from './types-9v-WZ_NU.js';
4
4
  import 'decimal.js';
5
+ import './locale.js';
5
6
  import './release-CyapqJ3z.js';
6
7
  import 'zod';
7
8
  import './time.js';
@@ -44,20 +45,49 @@ declare function usePluginTrpc(pluginId: string): any;
44
45
  */
45
46
  declare function useHostTrpc(): any;
46
47
  interface PluginI18nValue {
48
+ pluginId?: string | undefined;
49
+ namespace?: string | undefined;
47
50
  locale: string;
48
51
  timezone: string;
49
52
  isLoading: boolean;
53
+ availableLocales: readonly string[];
54
+ languages: readonly PluginLanguageOption[];
50
55
  t: (key: string, paramsOrDefault?: Record<string, string | number> | string) => string;
56
+ /** Host-bound Core common translator. Optional for compatibility with older Hosts. */
57
+ tCommon?: (key: string, paramsOrDefault?: Record<string, string | number> | string) => string;
51
58
  addNamespace: (namespace: string) => void;
52
59
  }
60
+ interface PluginLanguageOption {
61
+ locale: string;
62
+ name: string;
63
+ nativeName: string;
64
+ direction: 'ltr' | 'rtl';
65
+ isDefault: boolean;
66
+ sortOrder: number;
67
+ }
53
68
  declare const PluginI18nContext: React.Context<PluginI18nValue>;
54
69
  declare function usePluginTranslation(pluginId: string, customNamespace?: string): {
55
70
  t: (key: string, paramsOrDefault?: Record<string, string | number> | string) => string;
71
+ tCommon: (key: string, paramsOrDefault?: Record<string, string | number> | string) => string;
56
72
  locale: string;
57
73
  timezone: string;
58
74
  isLoading: boolean;
75
+ availableLocales: readonly string[];
76
+ languages: readonly PluginLanguageOption[];
59
77
  namespace: string;
60
78
  };
79
+ /** Currency capabilities exposed by the Admin Host to plugin UIs. */
80
+ interface PluginCurrencyValue {
81
+ currency: CurrencyInfo;
82
+ currencies: CurrencyInfo[];
83
+ baseCurrency: CurrencyInfo;
84
+ isLoading: boolean;
85
+ isReady: boolean;
86
+ changeCurrency: (currencyCode: string) => void;
87
+ formatPrice: (cents: number, fromCurrency?: string | undefined, options?: FormatPriceOptions | undefined) => string;
88
+ }
89
+ declare const PluginCurrencyContext: React.Context<PluginCurrencyValue>;
90
+ declare function usePluginCurrency(): PluginCurrencyValue;
61
91
  interface PluginSlotProps {
62
92
  /** Slot name, e.g. 'shop.product.detail.actions' */
63
93
  name: string;
@@ -72,12 +102,29 @@ interface PluginSlotProps {
72
102
  /** Fallback when no extensions registered */
73
103
  fallback?: React.ReactNode;
74
104
  }
105
+ interface PluginSlotRegistry {
106
+ /** Returns whether at least one extension is registered for the slot. */
107
+ has: (name: string) => boolean;
108
+ /** Subscribes to extension registry changes. */
109
+ subscribe: (onStoreChange: () => void) => () => void;
110
+ }
75
111
  /**
76
112
  * Called by the Admin Host at startup to inject the PluginSlot component.
77
113
  * Plugin code should NOT call this directly.
78
114
  * @internal
79
115
  */
80
116
  declare function __setPluginSlot(component: React.ComponentType<PluginSlotProps>): void;
117
+ /**
118
+ * Called by the Admin Host at startup to expose reactive slot availability.
119
+ * Plugin code should NOT call this directly.
120
+ * @internal
121
+ */
122
+ declare function __setPluginSlotRegistry(registry: PluginSlotRegistry | null): void;
123
+ /**
124
+ * Returns whether the given slot currently has at least one registered extension.
125
+ * The value updates when plugins are registered, reloaded, disabled, or removed.
126
+ */
127
+ declare function usePluginSlot(name: string): boolean;
81
128
  /**
82
129
  * Renders all plugin extensions registered for the given slot.
83
130
  *
@@ -142,4 +189,4 @@ declare function useMediaPicker(): {
142
189
  openDialog: (options: MediaPickerOptions) => void;
143
190
  };
144
191
 
145
- export { type MediaPickerOptions, type PluginAbility, PluginAbilityContext, PluginI18nContext, type PluginI18nValue, PluginSlot, type PluginSlotProps, __setPluginSlot, __setTrpc, useHostTrpc, useMediaPicker, usePluginAbility, usePluginTranslation, usePluginTrpc };
192
+ export { type MediaPickerOptions, type PluginAbility, PluginAbilityContext, PluginCurrencyContext, type PluginCurrencyValue, PluginI18nContext, type PluginI18nValue, type PluginLanguageOption, PluginSlot, type PluginSlotProps, type PluginSlotRegistry, __setPluginSlot, __setPluginSlotRegistry, __setTrpc, useHostTrpc, useMediaPicker, usePluginAbility, usePluginCurrency, usePluginSlot, usePluginTranslation, usePluginTrpc };
package/dist/react.js CHANGED
@@ -1,8 +1,15 @@
1
+ import {
2
+ DEFAULT_CURRENCY
3
+ } from "./chunk-BVOTSKM2.js";
4
+ import "./chunk-BI7E5CVM.js";
5
+
1
6
  // src/react.ts
2
7
  import React from "react";
3
8
  var _trpc = null;
9
+ var _hostTrpcFacade = null;
4
10
  function __setTrpc(trpc) {
5
11
  _trpc = trpc;
12
+ _hostTrpcFacade = null;
6
13
  }
7
14
  function usePluginTrpc(pluginId) {
8
15
  if (!_trpc) {
@@ -19,13 +26,35 @@ function useHostTrpc() {
19
26
  "[plugin/react] tRPC not initialized. Ensure plugin is loaded within Admin Host context."
20
27
  );
21
28
  }
22
- return _trpc;
29
+ if (!_hostTrpcFacade) {
30
+ _hostTrpcFacade = new Proxy(_trpc, {
31
+ get(target, property, receiver) {
32
+ if (property === "i18n") {
33
+ throw new Error(
34
+ "[plugin/react] Host i18n APIs are not exposed to plugins. Use the Host-bound usePluginTranslation() bridge."
35
+ );
36
+ }
37
+ return Reflect.get(target, property, receiver);
38
+ },
39
+ has(target, property) {
40
+ return property === "i18n" ? false : Reflect.has(target, property);
41
+ },
42
+ ownKeys(target) {
43
+ return Reflect.ownKeys(target).filter((property) => property !== "i18n");
44
+ }
45
+ });
46
+ }
47
+ return _hostTrpcFacade;
23
48
  }
49
+ var fallbackPluginTranslate = (key, paramsOrDefault) => typeof paramsOrDefault === "string" ? paramsOrDefault : key;
24
50
  var defaultI18n = {
25
51
  locale: "en-US",
26
52
  timezone: "UTC",
27
53
  isLoading: false,
28
- t: (key, paramsOrDefault) => typeof paramsOrDefault === "string" ? paramsOrDefault : key,
54
+ availableLocales: [],
55
+ languages: [],
56
+ t: fallbackPluginTranslate,
57
+ tCommon: fallbackPluginTranslate,
29
58
  addNamespace: () => void 0
30
59
  };
31
60
  var PluginI18nContext = React.createContext(defaultI18n);
@@ -33,27 +62,88 @@ function normalizePluginNamespace(pluginId) {
33
62
  return `plugin.${pluginId}`;
34
63
  }
35
64
  function usePluginTranslation(pluginId, customNamespace) {
36
- const namespace = customNamespace || normalizePluginNamespace(pluginId);
37
65
  const i18n = React.useContext(PluginI18nContext);
38
- React.useEffect(() => {
39
- i18n.addNamespace(namespace);
40
- }, [i18n, namespace]);
66
+ if (!i18n.pluginId || !i18n.namespace) {
67
+ throw new Error("[plugin/react] Plugin i18n boundary is missing");
68
+ }
69
+ if (pluginId !== i18n.pluginId) {
70
+ throw new Error(
71
+ `[plugin/react] Plugin i18n identity mismatch: expected ${i18n.pluginId}, received ${pluginId}`
72
+ );
73
+ }
74
+ const namespace = normalizePluginNamespace(pluginId);
75
+ if (customNamespace && customNamespace !== namespace) {
76
+ throw new Error(
77
+ `[plugin/react] Plugin namespace mismatch: expected ${namespace}, received ${customNamespace}`
78
+ );
79
+ }
80
+ if (i18n.namespace !== namespace) {
81
+ throw new Error(
82
+ `[plugin/react] Host-bound namespace mismatch: expected ${namespace}, received ${i18n.namespace}`
83
+ );
84
+ }
41
85
  const t = React.useCallback(
42
86
  (key, paramsOrDefault) => {
43
- const fullKey = key.startsWith(`${namespace}.`) ? key : `${namespace}.${key}`;
44
- return i18n.t(fullKey, paramsOrDefault);
87
+ const prefix = `${namespace}.`;
88
+ const localKey = key.startsWith(prefix) ? key.slice(prefix.length) : key;
89
+ return i18n.t(localKey, paramsOrDefault);
45
90
  },
46
- [i18n, namespace]
91
+ [i18n]
47
92
  );
93
+ const tCommon = i18n.tCommon ?? fallbackPluginTranslate;
48
94
  return React.useMemo(
49
- () => ({ t, locale: i18n.locale, timezone: i18n.timezone, isLoading: i18n.isLoading, namespace }),
50
- [t, i18n.locale, i18n.timezone, i18n.isLoading, namespace]
95
+ () => ({
96
+ t,
97
+ tCommon,
98
+ locale: i18n.locale,
99
+ timezone: i18n.timezone,
100
+ isLoading: i18n.isLoading,
101
+ availableLocales: i18n.availableLocales,
102
+ languages: i18n.languages,
103
+ namespace
104
+ }),
105
+ [
106
+ t,
107
+ tCommon,
108
+ i18n.locale,
109
+ i18n.timezone,
110
+ i18n.isLoading,
111
+ i18n.availableLocales,
112
+ i18n.languages,
113
+ namespace
114
+ ]
51
115
  );
52
116
  }
117
+ var defaultPluginCurrency = {
118
+ currency: DEFAULT_CURRENCY,
119
+ currencies: [],
120
+ baseCurrency: DEFAULT_CURRENCY,
121
+ isLoading: false,
122
+ isReady: false,
123
+ changeCurrency: () => void 0,
124
+ formatPrice: (cents, fromCurrency) => `${fromCurrency ?? DEFAULT_CURRENCY.code} ${(cents / 100).toFixed(2)}`
125
+ };
126
+ var PluginCurrencyContext = React.createContext(defaultPluginCurrency);
127
+ function usePluginCurrency() {
128
+ return React.useContext(PluginCurrencyContext);
129
+ }
53
130
  var _PluginSlotImpl = null;
131
+ var _pluginSlotRegistry = null;
132
+ var emptyPluginSlotSubscribe = () => () => void 0;
54
133
  function __setPluginSlot(component) {
55
134
  _PluginSlotImpl = component;
56
135
  }
136
+ function __setPluginSlotRegistry(registry) {
137
+ _pluginSlotRegistry = registry;
138
+ }
139
+ function usePluginSlot(name) {
140
+ const getSnapshot = () => _pluginSlotRegistry?.has(name) ?? false;
141
+ return React.useSyncExternalStore(
142
+ _pluginSlotRegistry?.subscribe ?? emptyPluginSlotSubscribe,
143
+ getSnapshot,
144
+ getSnapshot
145
+ );
146
+ }
57
147
  function PluginSlot(props) {
58
148
  if (!_PluginSlotImpl) {
59
149
  return null;
@@ -81,13 +171,17 @@ function useMediaPicker() {
81
171
  }
82
172
  export {
83
173
  PluginAbilityContext,
174
+ PluginCurrencyContext,
84
175
  PluginI18nContext,
85
176
  PluginSlot,
86
177
  __setPluginSlot,
178
+ __setPluginSlotRegistry,
87
179
  __setTrpc,
88
180
  useHostTrpc,
89
181
  useMediaPicker,
90
182
  usePluginAbility,
183
+ usePluginCurrency,
184
+ usePluginSlot,
91
185
  usePluginTranslation,
92
186
  usePluginTrpc
93
187
  };
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React utilities for plugin admin UIs.\n *\n * At runtime, this module is shared via Module Federation from the admin host.\n * Plugins should configure MF shared with `import: false` for this package.\n */\nimport React from 'react';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _trpc: any = null;\n\n/**\n * Called by the Admin Host at startup to inject the host's tRPC React client.\n * Plugin code should NOT call this directly.\n * @internal\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function __setTrpc(trpc: any) {\n _trpc = trpc;\n}\n\n/**\n * Hook for plugin components to access their tRPC namespace with React Query hooks.\n * Must be called within a component rendered inside the Admin Host.\n *\n * @param pluginId - Short plugin ID (e.g., 'storage-s3') or full ID (e.g., 'com.wordrhyme.storage-s3')\n * @returns tRPC proxy with useQuery/useMutation hooks\n *\n * @example\n * ```tsx\n * import { usePluginTrpc } from '@wordrhyme/plugin/react';\n *\n * function MyPluginPage() {\n * const api = usePluginTrpc('my-plugin');\n * const { data, isLoading } = api.listItems.useQuery();\n * const createItem = api.createItem.useMutation();\n * }\n * ```\n */\nexport function usePluginTrpc(pluginId: string) {\n if (!_trpc) {\n throw new Error(\n '[plugin/react] tRPC not initialized. ' +\n 'Ensure plugin is loaded within Admin Host context.',\n );\n }\n const normalizedId = pluginId\n .replace(/^com\\.wordrhyme\\./, '')\n .replace(/\\./g, '-');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (_trpc as any).pluginApis[normalizedId];\n}\n\n/**\n * Hook for plugin components to access the Admin Host's root tRPC React client.\n * Useful for shared host capabilities such as currency, media, auth, etc.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useHostTrpc(): any {\n if (!_trpc) {\n throw new Error(\n '[plugin/react] tRPC not initialized. ' +\n 'Ensure plugin is loaded within Admin Host context.',\n );\n }\n return _trpc;\n}\n\n// ============================================================\n// I18n — host translation bridge\n// ============================================================\n\nexport interface PluginI18nValue {\n locale: string;\n timezone: string;\n isLoading: boolean;\n t: (key: string, paramsOrDefault?: Record<string, string | number> | string) => string;\n addNamespace: (namespace: string) => void;\n}\n\nconst defaultI18n: PluginI18nValue = {\n locale: 'en-US',\n timezone: 'UTC',\n isLoading: false,\n t: (key, paramsOrDefault) => (typeof paramsOrDefault === 'string' ? paramsOrDefault : key),\n addNamespace: () => undefined,\n};\n\nexport const PluginI18nContext = React.createContext<PluginI18nValue>(defaultI18n);\n\nfunction normalizePluginNamespace(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nexport function usePluginTranslation(pluginId: string, customNamespace?: string) {\n const namespace = customNamespace || normalizePluginNamespace(pluginId);\n const i18n = React.useContext(PluginI18nContext);\n\n React.useEffect(() => {\n i18n.addNamespace(namespace);\n }, [i18n, namespace]);\n\n const t = React.useCallback(\n (key: string, paramsOrDefault?: Record<string, string | number> | string): string => {\n const fullKey = key.startsWith(`${namespace}.`) ? key : `${namespace}.${key}`;\n return i18n.t(fullKey, paramsOrDefault);\n },\n [i18n, namespace],\n );\n\n return React.useMemo(\n () => ({ t, locale: i18n.locale, timezone: i18n.timezone, isLoading: i18n.isLoading, namespace }),\n [t, i18n.locale, i18n.timezone, i18n.isLoading, namespace],\n );\n}\n\n// ============================================================\n// PluginSlot — runtime-injected from Admin Host\n// ============================================================\n\nexport interface PluginSlotProps {\n /** Slot name, e.g. 'shop.product.detail.actions' */\n name: string;\n /** React props passed to extensions rendered in this slot */\n props?: Record<string, unknown>;\n /** @deprecated Use props. Kept for legacy plugin slot call sites. */\n context?: Record<string, unknown>;\n /** Layout mode */\n layout?: 'inline' | 'stack' | 'tabs' | 'grid';\n /** Additional CSS class */\n className?: string;\n /** Fallback when no extensions registered */\n fallback?: React.ReactNode;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _PluginSlotImpl: React.ComponentType<PluginSlotProps> | null = null;\n\n/**\n * Called by the Admin Host at startup to inject the PluginSlot component.\n * Plugin code should NOT call this directly.\n * @internal\n */\nexport function __setPluginSlot(component: React.ComponentType<PluginSlotProps>) {\n _PluginSlotImpl = component;\n}\n\n/**\n * Renders all plugin extensions registered for the given slot.\n *\n * @example\n * ```tsx\n * import { PluginSlot } from '@wordrhyme/plugin/react';\n *\n * function ProductDetailPage({ productId }) {\n * return (\n * <div>\n * <h1>Product Detail</h1>\n * <PluginSlot\n * name=\"shop.product.detail.actions\"\n * layout=\"inline\"\n * props={{ productId }}\n * />\n * </div>\n * );\n * }\n * ```\n */\nexport function PluginSlot(props: PluginSlotProps): React.ReactElement | null {\n if (!_PluginSlotImpl) {\n // Silently render nothing if host hasn't injected yet\n return null;\n }\n return React.createElement(_PluginSlotImpl, props);\n}\n\n// Ability — runtime-injected from Admin Host (CASL permission)\n// ============================================================\n\n/**\n * Minimal ability interface for plugin permission checks.\n * Matches CASL MongoAbility.can() signature.\n */\nexport interface PluginAbility {\n can(action: string, subject: string): boolean;\n}\n\n/** Default allow-all ability (when host hasn't injected) */\nconst defaultAbility: PluginAbility = { can: () => true };\n\n/**\n * React Context for ability — shared between host and plugins.\n * Host injects via AbilityBridge, plugins consume via usePluginAbility.\n */\nexport const PluginAbilityContext = React.createContext<PluginAbility>(defaultAbility);\n\n/**\n * Hook for plugin components to check permissions.\n *\n * @example\n * ```tsx\n * import { usePluginAbility } from '@wordrhyme/plugin/react';\n *\n * function MyPluginPage() {\n * const ability = usePluginAbility();\n * const canCreate = ability.can('create', 'PluginResource');\n * const canDelete = ability.can('delete', 'PluginResource');\n * }\n * ```\n */\nexport function usePluginAbility(): PluginAbility {\n return React.useContext(PluginAbilityContext);\n}\n\n// ============================================================\n// Media Picker — accesses global imperative API\n// ============================================================\n\nimport type { PluginMediaInfo } from './types';\n\n// Define the options for the global MediaPicker\nexport interface MediaPickerOptions {\n presentation?: 'dialog' | 'drawer'; // Where to open the picker\n mode?: 'single' | 'multi';\n category?: 'all' | 'image' | 'document' | 'video' | 'audio' | 'archive' | 'other';\n onSelect: (media: PluginMediaInfo[]) => void;\n}\n\n/**\n * Hook to open the global Media Picker dialog (rendered by Admin Host).\n * Returns an imperative API `openDialog(options)`.\n */\nexport function useMediaPicker() {\n return {\n openDialog: (options: MediaPickerOptions) => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n if (typeof window !== 'undefined') {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const picker = window.__WORDRHYME_MEDIA_PICKER__ || window.__OMNIDS_MEDIA_PICKER__;\n if (picker) {\n picker.open(options);\n return;\n }\n }\n console.warn('[plugin/react] Media Picker is not mounted by the Admin Host.');\n }\n };\n}\n"],"mappings":";AAMA,OAAO,WAAW;AAGlB,IAAI,QAAa;AAQV,SAAS,UAAU,MAAW;AACjC,UAAQ;AACZ;AAoBO,SAAS,cAAc,UAAkB;AAC5C,MAAI,CAAC,OAAO;AACR,UAAM,IAAI;AAAA,MACN;AAAA,IAEJ;AAAA,EACJ;AACA,QAAM,eAAe,SAChB,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,OAAO,GAAG;AAEvB,SAAQ,MAAc,WAAW,YAAY;AACjD;AAOO,SAAS,cAAmB;AAC/B,MAAI,CAAC,OAAO;AACR,UAAM,IAAI;AAAA,MACN;AAAA,IAEJ;AAAA,EACJ;AACA,SAAO;AACX;AAcA,IAAM,cAA+B;AAAA,EACjC,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,WAAW;AAAA,EACX,GAAG,CAAC,KAAK,oBAAqB,OAAO,oBAAoB,WAAW,kBAAkB;AAAA,EACtF,cAAc,MAAM;AACxB;AAEO,IAAM,oBAAoB,MAAM,cAA+B,WAAW;AAEjF,SAAS,yBAAyB,UAA0B;AACxD,SAAO,UAAU,QAAQ;AAC7B;AAEO,SAAS,qBAAqB,UAAkB,iBAA0B;AAC7E,QAAM,YAAY,mBAAmB,yBAAyB,QAAQ;AACtE,QAAM,OAAO,MAAM,WAAW,iBAAiB;AAE/C,QAAM,UAAU,MAAM;AAClB,SAAK,aAAa,SAAS;AAAA,EAC/B,GAAG,CAAC,MAAM,SAAS,CAAC;AAEpB,QAAM,IAAI,MAAM;AAAA,IACZ,CAAC,KAAa,oBAAuE;AACjF,YAAM,UAAU,IAAI,WAAW,GAAG,SAAS,GAAG,IAAI,MAAM,GAAG,SAAS,IAAI,GAAG;AAC3E,aAAO,KAAK,EAAE,SAAS,eAAe;AAAA,IAC1C;AAAA,IACA,CAAC,MAAM,SAAS;AAAA,EACpB;AAEA,SAAO,MAAM;AAAA,IACT,OAAO,EAAE,GAAG,QAAQ,KAAK,QAAQ,UAAU,KAAK,UAAU,WAAW,KAAK,WAAW,UAAU;AAAA,IAC/F,CAAC,GAAG,KAAK,QAAQ,KAAK,UAAU,KAAK,WAAW,SAAS;AAAA,EAC7D;AACJ;AAsBA,IAAI,kBAA+D;AAO5D,SAAS,gBAAgB,WAAiD;AAC7E,oBAAkB;AACtB;AAuBO,SAAS,WAAW,OAAmD;AAC1E,MAAI,CAAC,iBAAiB;AAElB,WAAO;AAAA,EACX;AACA,SAAO,MAAM,cAAc,iBAAiB,KAAK;AACrD;AAcA,IAAM,iBAAgC,EAAE,KAAK,MAAM,KAAK;AAMjD,IAAM,uBAAuB,MAAM,cAA6B,cAAc;AAgB9E,SAAS,mBAAkC;AAC9C,SAAO,MAAM,WAAW,oBAAoB;AAChD;AAoBO,SAAS,iBAAiB;AAC7B,SAAO;AAAA,IACH,YAAY,CAAC,YAAgC;AAGzC,UAAI,OAAO,WAAW,aAAa;AAG/B,cAAM,SAAS,OAAO,8BAA8B,OAAO;AAC3D,YAAI,QAAQ;AACR,iBAAO,KAAK,OAAO;AACnB;AAAA,QACJ;AAAA,MACJ;AACA,cAAQ,KAAK,+DAA+D;AAAA,IAChF;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React utilities for plugin admin UIs.\n *\n * At runtime, this module is shared via Module Federation from the admin host.\n * Plugins should configure MF shared with `import: false` for this package.\n */\nimport React from 'react';\nimport {\n DEFAULT_CURRENCY,\n type CurrencyInfo,\n type FormatPriceOptions,\n} from './globalization';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _trpc: any = null;\nlet _hostTrpcFacade: any = null;\n\n/**\n * Called by the Admin Host at startup to inject the host's tRPC React client.\n * Plugin code should NOT call this directly.\n * @internal\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function __setTrpc(trpc: any) {\n _trpc = trpc;\n _hostTrpcFacade = null;\n}\n\n/**\n * Hook for plugin components to access their tRPC namespace with React Query hooks.\n * Must be called within a component rendered inside the Admin Host.\n *\n * @param pluginId - Short plugin ID (e.g., 'storage-s3') or full ID (e.g., 'com.wordrhyme.storage-s3')\n * @returns tRPC proxy with useQuery/useMutation hooks\n *\n * @example\n * ```tsx\n * import { usePluginTrpc } from '@wordrhyme/plugin/react';\n *\n * function MyPluginPage() {\n * const api = usePluginTrpc('my-plugin');\n * const { data, isLoading } = api.listItems.useQuery();\n * const createItem = api.createItem.useMutation();\n * }\n * ```\n */\nexport function usePluginTrpc(pluginId: string) {\n if (!_trpc) {\n throw new Error(\n '[plugin/react] tRPC not initialized. ' +\n 'Ensure plugin is loaded within Admin Host context.',\n );\n }\n const normalizedId = pluginId\n .replace(/^com\\.wordrhyme\\./, '')\n .replace(/\\./g, '-');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (_trpc as any).pluginApis[normalizedId];\n}\n\n/**\n * Hook for plugin components to access the Admin Host's root tRPC React client.\n * Useful for shared host capabilities such as currency, media, auth, etc.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useHostTrpc(): any {\n if (!_trpc) {\n throw new Error(\n '[plugin/react] tRPC not initialized. ' +\n 'Ensure plugin is loaded within Admin Host context.',\n );\n }\n if (!_hostTrpcFacade) {\n _hostTrpcFacade = new Proxy(_trpc, {\n get(target, property, receiver) {\n if (property === 'i18n') {\n throw new Error(\n '[plugin/react] Host i18n APIs are not exposed to plugins. ' +\n 'Use the Host-bound usePluginTranslation() bridge.',\n );\n }\n return Reflect.get(target, property, receiver);\n },\n has(target, property) {\n return property === 'i18n' ? false : Reflect.has(target, property);\n },\n ownKeys(target) {\n return Reflect.ownKeys(target).filter((property) => property !== 'i18n');\n },\n });\n }\n return _hostTrpcFacade;\n}\n\n// ============================================================\n// I18n — host translation bridge\n// ============================================================\n\nexport interface PluginI18nValue {\n pluginId?: string | undefined;\n namespace?: string | undefined;\n locale: string;\n timezone: string;\n isLoading: boolean;\n availableLocales: readonly string[];\n languages: readonly PluginLanguageOption[];\n t: (key: string, paramsOrDefault?: Record<string, string | number> | string) => string;\n /** Host-bound Core common translator. Optional for compatibility with older Hosts. */\n tCommon?: (key: string, paramsOrDefault?: Record<string, string | number> | string) => string;\n addNamespace: (namespace: string) => void;\n}\n\nexport interface PluginLanguageOption {\n locale: string;\n name: string;\n nativeName: string;\n direction: 'ltr' | 'rtl';\n isDefault: boolean;\n sortOrder: number;\n}\n\nconst fallbackPluginTranslate = (\n key: string,\n paramsOrDefault?: Record<string, string | number> | string,\n) => (typeof paramsOrDefault === 'string' ? paramsOrDefault : key);\n\nconst defaultI18n: PluginI18nValue = {\n locale: 'en-US',\n timezone: 'UTC',\n isLoading: false,\n availableLocales: [],\n languages: [],\n t: fallbackPluginTranslate,\n tCommon: fallbackPluginTranslate,\n addNamespace: () => undefined,\n};\n\nexport const PluginI18nContext = React.createContext<PluginI18nValue>(defaultI18n);\n\nfunction normalizePluginNamespace(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nexport function usePluginTranslation(pluginId: string, customNamespace?: string) {\n const i18n = React.useContext(PluginI18nContext);\n if (!i18n.pluginId || !i18n.namespace) {\n throw new Error('[plugin/react] Plugin i18n boundary is missing');\n }\n if (pluginId !== i18n.pluginId) {\n throw new Error(\n `[plugin/react] Plugin i18n identity mismatch: expected ${i18n.pluginId}, received ${pluginId}`,\n );\n }\n const namespace = normalizePluginNamespace(pluginId);\n if (customNamespace && customNamespace !== namespace) {\n throw new Error(\n `[plugin/react] Plugin namespace mismatch: expected ${namespace}, received ${customNamespace}`,\n );\n }\n if (i18n.namespace !== namespace) {\n throw new Error(\n `[plugin/react] Host-bound namespace mismatch: expected ${namespace}, received ${i18n.namespace}`,\n );\n }\n\n const t = React.useCallback(\n (key: string, paramsOrDefault?: Record<string, string | number> | string): string => {\n const prefix = `${namespace}.`;\n const localKey = key.startsWith(prefix) ? key.slice(prefix.length) : key;\n return i18n.t(localKey, paramsOrDefault);\n },\n [i18n],\n );\n const tCommon = i18n.tCommon ?? fallbackPluginTranslate;\n\n return React.useMemo(\n () => ({\n t,\n tCommon,\n locale: i18n.locale,\n timezone: i18n.timezone,\n isLoading: i18n.isLoading,\n availableLocales: i18n.availableLocales,\n languages: i18n.languages,\n namespace,\n }),\n [\n t,\n tCommon,\n i18n.locale,\n i18n.timezone,\n i18n.isLoading,\n i18n.availableLocales,\n i18n.languages,\n namespace,\n ],\n );\n}\n\n// ============================================================\n// Currency — host currency bridge\n// ============================================================\n\n/** Currency capabilities exposed by the Admin Host to plugin UIs. */\nexport interface PluginCurrencyValue {\n currency: CurrencyInfo;\n currencies: CurrencyInfo[];\n baseCurrency: CurrencyInfo;\n isLoading: boolean;\n isReady: boolean;\n changeCurrency: (currencyCode: string) => void;\n formatPrice: (\n cents: number,\n fromCurrency?: string | undefined,\n options?: FormatPriceOptions | undefined,\n ) => string;\n}\n\nconst defaultPluginCurrency: PluginCurrencyValue = {\n currency: DEFAULT_CURRENCY,\n currencies: [],\n baseCurrency: DEFAULT_CURRENCY,\n isLoading: false,\n isReady: false,\n changeCurrency: () => undefined,\n formatPrice: (cents, fromCurrency) => `${fromCurrency ?? DEFAULT_CURRENCY.code} ${(cents / 100).toFixed(2)}`,\n};\n\nexport const PluginCurrencyContext = React.createContext<PluginCurrencyValue>(defaultPluginCurrency);\n\nexport function usePluginCurrency(): PluginCurrencyValue {\n return React.useContext(PluginCurrencyContext);\n}\n\n// ============================================================\n// PluginSlot — runtime-injected from Admin Host\n// ============================================================\n\nexport interface PluginSlotProps {\n /** Slot name, e.g. 'shop.product.detail.actions' */\n name: string;\n /** React props passed to extensions rendered in this slot */\n props?: Record<string, unknown>;\n /** @deprecated Use props. Kept for legacy plugin slot call sites. */\n context?: Record<string, unknown>;\n /** Layout mode */\n layout?: 'inline' | 'stack' | 'tabs' | 'grid';\n /** Additional CSS class */\n className?: string;\n /** Fallback when no extensions registered */\n fallback?: React.ReactNode;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _PluginSlotImpl: React.ComponentType<PluginSlotProps> | null = null;\n\nexport interface PluginSlotRegistry {\n /** Returns whether at least one extension is registered for the slot. */\n has: (name: string) => boolean;\n /** Subscribes to extension registry changes. */\n subscribe: (onStoreChange: () => void) => () => void;\n}\n\nlet _pluginSlotRegistry: PluginSlotRegistry | null = null;\nconst emptyPluginSlotSubscribe = () => () => undefined;\n\n/**\n * Called by the Admin Host at startup to inject the PluginSlot component.\n * Plugin code should NOT call this directly.\n * @internal\n */\nexport function __setPluginSlot(component: React.ComponentType<PluginSlotProps>) {\n _PluginSlotImpl = component;\n}\n\n/**\n * Called by the Admin Host at startup to expose reactive slot availability.\n * Plugin code should NOT call this directly.\n * @internal\n */\nexport function __setPluginSlotRegistry(registry: PluginSlotRegistry | null) {\n _pluginSlotRegistry = registry;\n}\n\n/**\n * Returns whether the given slot currently has at least one registered extension.\n * The value updates when plugins are registered, reloaded, disabled, or removed.\n */\nexport function usePluginSlot(name: string): boolean {\n const getSnapshot = () => _pluginSlotRegistry?.has(name) ?? false;\n\n return React.useSyncExternalStore(\n _pluginSlotRegistry?.subscribe ?? emptyPluginSlotSubscribe,\n getSnapshot,\n getSnapshot,\n );\n}\n\n/**\n * Renders all plugin extensions registered for the given slot.\n *\n * @example\n * ```tsx\n * import { PluginSlot } from '@wordrhyme/plugin/react';\n *\n * function ProductDetailPage({ productId }) {\n * return (\n * <div>\n * <h1>Product Detail</h1>\n * <PluginSlot\n * name=\"shop.product.detail.actions\"\n * layout=\"inline\"\n * props={{ productId }}\n * />\n * </div>\n * );\n * }\n * ```\n */\nexport function PluginSlot(props: PluginSlotProps): React.ReactElement | null {\n if (!_PluginSlotImpl) {\n // Silently render nothing if host hasn't injected yet\n return null;\n }\n return React.createElement(_PluginSlotImpl, props);\n}\n\n// Ability — runtime-injected from Admin Host (CASL permission)\n// ============================================================\n\n/**\n * Minimal ability interface for plugin permission checks.\n * Matches CASL MongoAbility.can() signature.\n */\nexport interface PluginAbility {\n can(action: string, subject: string): boolean;\n}\n\n/** Default allow-all ability (when host hasn't injected) */\nconst defaultAbility: PluginAbility = { can: () => true };\n\n/**\n * React Context for ability — shared between host and plugins.\n * Host injects via AbilityBridge, plugins consume via usePluginAbility.\n */\nexport const PluginAbilityContext = React.createContext<PluginAbility>(defaultAbility);\n\n/**\n * Hook for plugin components to check permissions.\n *\n * @example\n * ```tsx\n * import { usePluginAbility } from '@wordrhyme/plugin/react';\n *\n * function MyPluginPage() {\n * const ability = usePluginAbility();\n * const canCreate = ability.can('create', 'PluginResource');\n * const canDelete = ability.can('delete', 'PluginResource');\n * }\n * ```\n */\nexport function usePluginAbility(): PluginAbility {\n return React.useContext(PluginAbilityContext);\n}\n\n// ============================================================\n// Media Picker — accesses global imperative API\n// ============================================================\n\nimport type { PluginMediaInfo } from './types';\n\n// Define the options for the global MediaPicker\nexport interface MediaPickerOptions {\n presentation?: 'dialog' | 'drawer'; // Where to open the picker\n mode?: 'single' | 'multi';\n category?: 'all' | 'image' | 'document' | 'video' | 'audio' | 'archive' | 'other';\n onSelect: (media: PluginMediaInfo[]) => void;\n}\n\n/**\n * Hook to open the global Media Picker dialog (rendered by Admin Host).\n * Returns an imperative API `openDialog(options)`.\n */\nexport function useMediaPicker() {\n return {\n openDialog: (options: MediaPickerOptions) => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n if (typeof window !== 'undefined') {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const picker = window.__WORDRHYME_MEDIA_PICKER__ || window.__OMNIDS_MEDIA_PICKER__;\n if (picker) {\n picker.open(options);\n return;\n }\n }\n console.warn('[plugin/react] Media Picker is not mounted by the Admin Host.');\n }\n };\n}\n"],"mappings":";;;;;;AAMA,OAAO,WAAW;AAQlB,IAAI,QAAa;AACjB,IAAI,kBAAuB;AAQpB,SAAS,UAAU,MAAW;AACjC,UAAQ;AACR,oBAAkB;AACtB;AAoBO,SAAS,cAAc,UAAkB;AAC5C,MAAI,CAAC,OAAO;AACR,UAAM,IAAI;AAAA,MACN;AAAA,IAEJ;AAAA,EACJ;AACA,QAAM,eAAe,SAChB,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,OAAO,GAAG;AAEvB,SAAQ,MAAc,WAAW,YAAY;AACjD;AAOO,SAAS,cAAmB;AAC/B,MAAI,CAAC,OAAO;AACR,UAAM,IAAI;AAAA,MACN;AAAA,IAEJ;AAAA,EACJ;AACA,MAAI,CAAC,iBAAiB;AAClB,sBAAkB,IAAI,MAAM,OAAO;AAAA,MAC/B,IAAI,QAAQ,UAAU,UAAU;AAC5B,YAAI,aAAa,QAAQ;AACrB,gBAAM,IAAI;AAAA,YACN;AAAA,UAEJ;AAAA,QACJ;AACA,eAAO,QAAQ,IAAI,QAAQ,UAAU,QAAQ;AAAA,MACjD;AAAA,MACA,IAAI,QAAQ,UAAU;AAClB,eAAO,aAAa,SAAS,QAAQ,QAAQ,IAAI,QAAQ,QAAQ;AAAA,MACrE;AAAA,MACA,QAAQ,QAAQ;AACZ,eAAO,QAAQ,QAAQ,MAAM,EAAE,OAAO,CAAC,aAAa,aAAa,MAAM;AAAA,MAC3E;AAAA,IACJ,CAAC;AAAA,EACL;AACA,SAAO;AACX;AA6BA,IAAM,0BAA0B,CAC5B,KACA,oBACE,OAAO,oBAAoB,WAAW,kBAAkB;AAE9D,IAAM,cAA+B;AAAA,EACjC,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,WAAW;AAAA,EACX,kBAAkB,CAAC;AAAA,EACnB,WAAW,CAAC;AAAA,EACZ,GAAG;AAAA,EACH,SAAS;AAAA,EACT,cAAc,MAAM;AACxB;AAEO,IAAM,oBAAoB,MAAM,cAA+B,WAAW;AAEjF,SAAS,yBAAyB,UAA0B;AACxD,SAAO,UAAU,QAAQ;AAC7B;AAEO,SAAS,qBAAqB,UAAkB,iBAA0B;AAC7E,QAAM,OAAO,MAAM,WAAW,iBAAiB;AAC/C,MAAI,CAAC,KAAK,YAAY,CAAC,KAAK,WAAW;AACnC,UAAM,IAAI,MAAM,gDAAgD;AAAA,EACpE;AACA,MAAI,aAAa,KAAK,UAAU;AAC5B,UAAM,IAAI;AAAA,MACN,0DAA0D,KAAK,QAAQ,cAAc,QAAQ;AAAA,IACjG;AAAA,EACJ;AACA,QAAM,YAAY,yBAAyB,QAAQ;AACnD,MAAI,mBAAmB,oBAAoB,WAAW;AAClD,UAAM,IAAI;AAAA,MACN,sDAAsD,SAAS,cAAc,eAAe;AAAA,IAChG;AAAA,EACJ;AACA,MAAI,KAAK,cAAc,WAAW;AAC9B,UAAM,IAAI;AAAA,MACN,0DAA0D,SAAS,cAAc,KAAK,SAAS;AAAA,IACnG;AAAA,EACJ;AAEA,QAAM,IAAI,MAAM;AAAA,IACZ,CAAC,KAAa,oBAAuE;AACjF,YAAM,SAAS,GAAG,SAAS;AAC3B,YAAM,WAAW,IAAI,WAAW,MAAM,IAAI,IAAI,MAAM,OAAO,MAAM,IAAI;AACrE,aAAO,KAAK,EAAE,UAAU,eAAe;AAAA,IAC3C;AAAA,IACA,CAAC,IAAI;AAAA,EACT;AACA,QAAM,UAAU,KAAK,WAAW;AAEhC,SAAO,MAAM;AAAA,IACT,OAAO;AAAA,MACH;AAAA,MACA;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,MAChB,kBAAkB,KAAK;AAAA,MACvB,WAAW,KAAK;AAAA,MAChB;AAAA,IACJ;AAAA,IACA;AAAA,MACI;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ;AAqBA,IAAM,wBAA6C;AAAA,EAC/C,UAAU;AAAA,EACV,YAAY,CAAC;AAAA,EACb,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS;AAAA,EACT,gBAAgB,MAAM;AAAA,EACtB,aAAa,CAAC,OAAO,iBAAiB,GAAG,gBAAgB,iBAAiB,IAAI,KAAK,QAAQ,KAAK,QAAQ,CAAC,CAAC;AAC9G;AAEO,IAAM,wBAAwB,MAAM,cAAmC,qBAAqB;AAE5F,SAAS,oBAAyC;AACrD,SAAO,MAAM,WAAW,qBAAqB;AACjD;AAsBA,IAAI,kBAA+D;AASnE,IAAI,sBAAiD;AACrD,IAAM,2BAA2B,MAAM,MAAM;AAOtC,SAAS,gBAAgB,WAAiD;AAC7E,oBAAkB;AACtB;AAOO,SAAS,wBAAwB,UAAqC;AACzE,wBAAsB;AAC1B;AAMO,SAAS,cAAc,MAAuB;AACjD,QAAM,cAAc,MAAM,qBAAqB,IAAI,IAAI,KAAK;AAE5D,SAAO,MAAM;AAAA,IACT,qBAAqB,aAAa;AAAA,IAClC;AAAA,IACA;AAAA,EACJ;AACJ;AAuBO,SAAS,WAAW,OAAmD;AAC1E,MAAI,CAAC,iBAAiB;AAElB,WAAO;AAAA,EACX;AACA,SAAO,MAAM,cAAc,iBAAiB,KAAK;AACrD;AAcA,IAAM,iBAAgC,EAAE,KAAK,MAAM,KAAK;AAMjD,IAAM,uBAAuB,MAAM,cAA6B,cAAc;AAgB9E,SAAS,mBAAkC;AAC9C,SAAO,MAAM,WAAW,oBAAoB;AAChD;AAoBO,SAAS,iBAAiB;AAC7B,SAAO;AAAA,IACH,YAAY,CAAC,YAAgC;AAGzC,UAAI,OAAO,WAAW,aAAa;AAG/B,cAAM,SAAS,OAAO,8BAA8B,OAAO;AAC3D,YAAI,QAAQ;AACR,iBAAO,KAAK,OAAO;AACnB;AAAA,QACJ;AAAA,MACJ;AACA,cAAQ,KAAK,+DAA+D;AAAA,IAChF;AAAA,EACJ;AACJ;","names":[]}
package/dist/server.d.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  export { createPluginContext, pluginProcedure, pluginRouter } from './trpc.js';
2
- import { a as PluginContext } from './types-oPAs313y.js';
3
- export { H as HookContext, c as HookTransaction } from './types-oPAs313y.js';
2
+ import { a as PluginContext } from './types-9v-WZ_NU.js';
3
+ export { H as HookContext, c as HookTransaction } from './types-9v-WZ_NU.js';
4
4
  import { DateRangeResolver } from './time.js';
5
5
  export { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
6
- export { E as ENTITY_EXTENSION_VALUES_SAVE_HOOK_ID, a as EXTENSION_PROJECT_SAVE_HOOK_ID, b as EntityExtensionProjectionValue, c as EntityExtensionValueSaveHandler, d as EntityExtensionValueSavePayload, e as EntityExtensionValueSaveResult, f as entityExtensions, r as registerEntityExtensionValueSaver } from './entity-extensions-D09rvNgi.js';
6
+ export { E as ENTITY_EXTENSION_VALUES_SAVE_HOOK_ID, a as EXTENSION_PROJECT_SAVE_HOOK_ID, b as EntityExtensionProjectionValue, c as EntityExtensionValueSaveHandler, d as EntityExtensionValueSavePayload, e as EntityExtensionValueSaveResult, f as entityExtensions, r as registerEntityExtensionValueSaver } from './entity-extensions-gJuSTXYZ.js';
7
7
  import './globalization.js';
8
8
  import 'decimal.js';
9
+ import './locale.js';
9
10
  import './release-CyapqJ3z.js';
10
11
  import 'zod';
11
- import './manifest-zSGjFCcJ.js';
12
+ import './manifest-CDTCsMYV.js';
12
13
 
13
14
  interface PluginRuntimeRequestContext {
14
15
  requestId?: string | undefined;
package/dist/trpc.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as _trpc_server from '@trpc/server';
2
2
  export { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
3
- import { a as PluginContext } from './types-oPAs313y.js';
3
+ import { a as PluginContext } from './types-9v-WZ_NU.js';
4
4
  import './globalization.js';
5
5
  import 'decimal.js';
6
+ import './locale.js';
6
7
  import './release-CyapqJ3z.js';
7
8
  import 'zod';
8
9
  import './time.js';
@@ -221,6 +221,8 @@ interface WebPluginRouteRequest {
221
221
  currency?: string | undefined;
222
222
  timezone?: string | undefined;
223
223
  globalization?: GlobalizationState | undefined;
224
+ /** Host-bound translator for common + the current route owner namespace. */
225
+ t?: NonNullable<GlobalizationState["t"]> | undefined;
224
226
  tenant?: WebPluginTenantInfo | undefined;
225
227
  site?: WebPluginSiteInfo | undefined;
226
228
  renderSlot?: WebSlotRenderer | undefined;
@@ -512,6 +514,11 @@ type AiStreamEvent = {
512
514
  type: 'done';
513
515
  };
514
516
  interface PluginAiCapability {
517
+ /** Optional, backward-compatible availability probe for AI-enabled UI. */
518
+ status?(): Promise<{
519
+ available: boolean;
520
+ reason?: string | undefined;
521
+ }>;
515
522
  listModels(request?: AiModelListRequest): Promise<AiModelCatalog>;
516
523
  generateText(request: AiTextRequest): Promise<AiTextResult>;
517
524
  generateObject<T>(request: AiObjectRequest<T>): Promise<AiObjectResult<T>>;
@@ -552,6 +559,8 @@ interface AiRuntimeHost {
552
559
  recordUsage(input: AiUsageObservation): Promise<void>;
553
560
  }
554
561
  interface AiRuntimeHandler {
562
+ /** Read-only readiness probe. It validates the effective model and required credential without reserving usage. */
563
+ checkReady?(invocation: AiRuntimeInvocation, host: AiRuntimeHost): Promise<void>;
555
564
  listModels(invocation: AiRuntimeInvocation, request: AiModelListRequest, host: AiRuntimeHost): Promise<AiModelCatalog>;
556
565
  generateText(invocation: AiRuntimeInvocation, request: AiTextRequest, host: AiRuntimeHost): Promise<AiTextResult>;
557
566
  generateObject<T>(invocation: AiRuntimeInvocation, request: AiObjectRequest<T>, host: AiRuntimeHost): Promise<AiObjectResult<T>>;
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@wordrhyme/plugin",
3
- "version": "0.1.0-alpha.6",
3
+ "version": "0.1.0-alpha.7",
4
4
  "private": false,
5
5
  "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/denvey/wordrhyme.git",
9
+ "directory": "packages/plugin"
10
+ },
6
11
  "main": "./dist/index.js",
7
12
  "types": "./dist/index.d.ts",
8
13
  "exports": {
@@ -36,6 +41,10 @@
36
41
  "types": "./dist/globalization.d.ts",
37
42
  "import": "./dist/globalization.js"
38
43
  },
44
+ "./locale": {
45
+ "types": "./dist/locale.d.ts",
46
+ "import": "./dist/locale.js"
47
+ },
39
48
  "./time": {
40
49
  "types": "./dist/time.d.ts",
41
50
  "import": "./dist/time.js"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/globalization.ts"],"sourcesContent":["import Decimal from 'decimal.js';\n\nDecimal.set({\n precision: 28,\n rounding: Decimal.ROUND_HALF_EVEN,\n});\n\nexport type TextDirection = 'ltr' | 'rtl';\n\nexport type TranslationMessages = Record<string, string>;\n\nexport type I18nTextField = string | Record<string, string | null | undefined> | null | undefined;\n\nexport interface LanguageInfo {\n locale: string;\n name: string;\n nativeName?: string | null | undefined;\n isDefault?: boolean | undefined;\n isEnabled?: boolean | undefined;\n sortOrder?: number | null | undefined;\n direction?: TextDirection | undefined;\n}\n\nexport interface CurrencyInfo {\n code: string;\n nameI18n: Record<string, string>;\n symbol: string;\n decimalDigits: number;\n isBase: boolean;\n currentRate: string | null;\n}\n\nexport type CurrencyInfoInput = {\n code: string;\n nameI18n?: Record<string, string> | null | undefined;\n symbol?: string | null | undefined;\n decimalDigits?: number | null | undefined;\n isBase?: boolean | null | undefined;\n currentRate?: string | null | undefined;\n};\n\nexport interface GlobalizationState {\n locale: string;\n direction: TextDirection;\n availableLocales: string[];\n languages?: LanguageInfo[] | undefined;\n currency: string;\n currencies: CurrencyInfo[];\n baseCurrency: CurrencyInfo;\n timezone?: string | undefined;\n fallbackLocale?: string | undefined;\n messages?: TranslationMessages | undefined;\n t?: Translator | undefined;\n p?: PriceFormatter | undefined;\n changeLocale?: ((locale: string) => void | Promise<void>) | undefined;\n changeCurrency?: ((currencyCode: string) => void | Promise<void>) | undefined;\n}\n\nexport interface FormatPriceOptions {\n showSymbol?: boolean | undefined;\n currencyDisplay?: 'symbol' | 'code' | 'name' | 'narrowSymbol' | undefined;\n notation?: 'standard' | 'compact' | undefined;\n signDisplay?: 'auto' | 'always' | 'exceptZero' | 'never' | undefined;\n decimalDigits?: number | undefined;\n locale?: string | undefined;\n toCurrency?: string | CurrencyInfo | undefined;\n}\n\nexport type Translator = (\n key: string,\n paramsOrFallback?: Record<string, string | number> | string,\n) => string;\n\nexport type PriceFormatter = (\n cents: number,\n fromCurrencyOrOptions?: string | CurrencyInfo | FormatPriceOptions,\n options?: FormatPriceOptions,\n) => string;\n\nexport const DEFAULT_CURRENCY: CurrencyInfo = {\n code: 'USD',\n nameI18n: { 'en-US': 'US Dollar', 'zh-CN': '美元' },\n symbol: '$',\n decimalDigits: 2,\n isBase: true,\n currentRate: '1',\n};\n\nconst RTL_LANGUAGE_CODES = new Set(['ar', 'he', 'fa', 'ur']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value && typeof value === 'object' && !Array.isArray(value));\n}\n\nfunction isCurrencyInfo(value: unknown): value is CurrencyInfo {\n return isRecord(value) && typeof value['code'] === 'string';\n}\n\nfunction isCurrencyCatalog(value: readonly CurrencyInfo[] | CurrencyInfo): value is readonly CurrencyInfo[] {\n return Array.isArray(value);\n}\n\nfunction readRate(currency: CurrencyInfo): Decimal {\n if (currency.isBase) return new Decimal(1);\n\n try {\n const rate = new Decimal(currency.currentRate ?? '1');\n return rate.gt(0) ? rate : new Decimal(1);\n } catch {\n return new Decimal(1);\n }\n}\n\nfunction minorUnitFactor(currency: CurrencyInfo): Decimal {\n return new Decimal(10).pow(Math.max(0, currency.decimalDigits));\n}\n\nexport function getTextDirection(locale: string): TextDirection {\n const languageCode = locale.split('-')[0]?.toLowerCase();\n return languageCode && RTL_LANGUAGE_CODES.has(languageCode) ? 'rtl' : 'ltr';\n}\n\nexport function normalizeCurrencyInfo(\n currency: CurrencyInfoInput,\n fallback: CurrencyInfo = DEFAULT_CURRENCY,\n): CurrencyInfo {\n const code = currency.code.toUpperCase();\n return {\n code,\n nameI18n: currency.nameI18n ?? (code === fallback.code ? fallback.nameI18n : {}),\n symbol: currency.symbol ?? (code === fallback.code ? fallback.symbol : code),\n decimalDigits: typeof currency.decimalDigits === 'number' ? currency.decimalDigits : fallback.decimalDigits,\n isBase: currency.isBase ?? code === fallback.code,\n currentRate: currency.isBase ? '1' : (currency.currentRate ?? null),\n };\n}\n\nexport function normalizeCurrencies(currencies: readonly CurrencyInfoInput[]): CurrencyInfo[] {\n if (currencies.length === 0) return [DEFAULT_CURRENCY];\n const normalized = currencies.map((currency) => normalizeCurrencyInfo(currency));\n const hasBase = normalized.some((currency) => currency.isBase);\n if (hasBase) return normalized;\n\n return normalized.map((currency, index) => ({\n ...currency,\n isBase: index === 0,\n currentRate: index === 0 ? '1' : currency.currentRate,\n }));\n}\n\nexport function findCurrency(\n currencies: readonly CurrencyInfo[],\n currencyCode: string | undefined,\n): CurrencyInfo | undefined {\n if (!currencyCode) return undefined;\n const normalizedCode = currencyCode.toUpperCase();\n return currencies.find((currency) => currency.code.toUpperCase() === normalizedCode);\n}\n\nexport function getBaseCurrency(currencies: readonly CurrencyInfo[]): CurrencyInfo {\n return currencies.find((currency) => currency.isBase) ?? currencies[0] ?? DEFAULT_CURRENCY;\n}\n\nexport function resolveCurrencyInfo(\n currency: string | CurrencyInfo | undefined,\n currencies: readonly CurrencyInfo[],\n fallback: CurrencyInfo = getBaseCurrency(currencies),\n): CurrencyInfo {\n if (isCurrencyInfo(currency)) return normalizeCurrencyInfo(currency, fallback);\n return findCurrency(currencies, currency) ?? normalizeCurrencyInfo({ code: currency ?? fallback.code }, fallback);\n}\n\nexport function bankersRound(value: number, decimalDigits: number): number {\n const decimal = new Decimal(value);\n const factor = new Decimal(10).pow(decimalDigits);\n return decimal.times(factor).round().div(factor).toNumber();\n}\n\nexport function toCents(displayAmount: number, decimalDigits: number): number {\n const factor = new Decimal(10).pow(decimalDigits);\n return new Decimal(displayAmount).times(factor).round().toNumber();\n}\n\nexport function fromCents(cents: number, decimalDigits: number): number {\n const factor = new Decimal(10).pow(decimalDigits);\n return new Decimal(cents).div(factor).toNumber();\n}\n\nexport function convertCurrency(\n cents: number,\n fromCurrency: CurrencyInfo,\n toCurrency: CurrencyInfo,\n currenciesOrBaseCurrency: readonly CurrencyInfo[] | CurrencyInfo,\n): number {\n if (fromCurrency.code === toCurrency.code) return cents;\n\n const catalog = isCurrencyCatalog(currenciesOrBaseCurrency)\n ? currenciesOrBaseCurrency\n : [currenciesOrBaseCurrency, fromCurrency, toCurrency];\n const baseCurrency = isCurrencyCatalog(currenciesOrBaseCurrency)\n ? getBaseCurrency(catalog)\n : currenciesOrBaseCurrency;\n\n const from = normalizeCurrencyInfo(fromCurrency, baseCurrency);\n const to = normalizeCurrencyInfo(toCurrency, baseCurrency);\n const sourceMinor = new Decimal(cents);\n const sourceMajor = sourceMinor.div(minorUnitFactor(from));\n const baseMajor = sourceMajor.div(readRate(from));\n const targetMajor = baseMajor.times(readRate(to));\n return targetMajor.times(minorUnitFactor(to)).round().toNumber();\n}\n\nexport function formatCurrencyAmount(\n cents: number,\n currency: CurrencyInfo,\n locale: string,\n options: FormatPriceOptions = {},\n): string {\n const {\n showSymbol = true,\n currencyDisplay = 'symbol',\n notation = 'standard',\n signDisplay = 'auto',\n decimalDigits,\n } = options;\n const digits = decimalDigits ?? currency.decimalDigits;\n const displayAmount = fromCents(cents, currency.decimalDigits);\n\n try {\n const formatter = new Intl.NumberFormat(locale, showSymbol ? {\n style: 'currency',\n currency: currency.code,\n currencyDisplay,\n notation,\n signDisplay,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n } : {\n style: 'decimal',\n notation,\n signDisplay,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n });\n return formatter.format(displayAmount);\n } catch {\n const symbol = showSymbol ? currency.symbol : '';\n return `${symbol}${displayAmount.toFixed(digits)}`;\n }\n}\n\nexport function formatPrice(\n cents: number,\n fromCurrency: string | CurrencyInfo,\n state: GlobalizationState,\n options: FormatPriceOptions = {},\n): string {\n const currencies = normalizeCurrencies(state.currencies.length ? state.currencies : [state.baseCurrency]);\n const baseCurrency = normalizeCurrencyInfo(state.baseCurrency, getBaseCurrency(currencies));\n const sourceCurrency = resolveCurrencyInfo(fromCurrency, currencies, baseCurrency);\n const targetCurrency = resolveCurrencyInfo(options.toCurrency ?? state.currency, currencies, baseCurrency);\n const converted = convertCurrency(cents, sourceCurrency, targetCurrency, currencies);\n\n return formatCurrencyAmount(\n converted,\n targetCurrency,\n options.locale ?? state.locale,\n options,\n );\n}\n\nexport function getI18nText(\n field: I18nTextField,\n locale: string,\n fallbackLocale = 'en-US',\n): string {\n if (typeof field === 'string') return field;\n if (!field || typeof field !== 'object') return '';\n\n const languageCode = locale.split('-')[0];\n const fallbackLanguageCode = fallbackLocale.split('-')[0];\n const candidates = [\n field[locale],\n languageCode ? field[languageCode] : undefined,\n field[fallbackLocale],\n fallbackLanguageCode ? field[fallbackLanguageCode] : undefined,\n ...Object.values(field),\n ];\n\n return candidates.find((value): value is string => typeof value === 'string' && value.length > 0) ?? '';\n}\n\nexport function createTranslator(messages: TranslationMessages | undefined): Translator {\n const translations = messages ?? {};\n return (key, paramsOrFallback) => {\n let value = translations[key];\n if (!value) {\n return typeof paramsOrFallback === 'string' ? paramsOrFallback : key;\n }\n if (paramsOrFallback && typeof paramsOrFallback === 'object') {\n for (const [name, replacement] of Object.entries(paramsOrFallback)) {\n value = value.replace(new RegExp(`\\\\{${name}\\\\}`, 'g'), String(replacement));\n }\n }\n return value;\n };\n}\n\nfunction isFormatPriceOptions(value: unknown): value is FormatPriceOptions {\n return isRecord(value) && !('code' in value);\n}\n\nexport function createPriceFormatter(\n state: GlobalizationState,\n defaultFromCurrency?: string | CurrencyInfo,\n): PriceFormatter {\n return (cents, fromCurrencyOrOptions, options) => {\n const fromCurrency = isFormatPriceOptions(fromCurrencyOrOptions)\n ? defaultFromCurrency ?? state.baseCurrency\n : fromCurrencyOrOptions ?? defaultFromCurrency ?? state.baseCurrency;\n const formatOptions = isFormatPriceOptions(fromCurrencyOrOptions)\n ? fromCurrencyOrOptions\n : options;\n\n return formatPrice(cents, fromCurrency, state, formatOptions);\n };\n}\n\nexport function withGlobalizationHelpers(\n state: GlobalizationState,\n handlers: Pick<GlobalizationState, 'changeLocale' | 'changeCurrency'> = {},\n): GlobalizationState {\n return {\n ...state,\n t: state.t ?? createTranslator(state.messages),\n p: state.p ?? createPriceFormatter(state),\n ...handlers,\n };\n}\n\nexport function buildGlobalizationUrl(\n url: string | URL,\n updates: { locale?: string | null | undefined; currency?: string | null | undefined },\n): string {\n const raw = String(url);\n const isAbsolute = /^[a-z][a-z\\d+\\-.]*:/i.test(raw);\n const parsed = new URL(raw, 'https://wordrhyme.local');\n\n if (updates.locale === null) {\n parsed.searchParams.delete('lang');\n } else if (updates.locale) {\n parsed.searchParams.set('lang', updates.locale);\n }\n\n if (updates.currency === null) {\n parsed.searchParams.delete('currency');\n } else if (updates.currency) {\n parsed.searchParams.set('currency', updates.currency.toUpperCase());\n }\n\n return isAbsolute ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`;\n}\n\nexport { Decimal };\n"],"mappings":"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","names":[]}