@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.
- package/dist/artifact.d.ts +1 -1
- package/dist/artifact.js +2 -1
- package/dist/artifact.js.map +1 -1
- package/dist/chunk-BI7E5CVM.js +26 -0
- package/dist/chunk-BI7E5CVM.js.map +1 -0
- package/dist/{chunk-46KUIGB6.js → chunk-BVOTSKM2.js} +36 -12
- package/dist/chunk-BVOTSKM2.js.map +1 -0
- package/dist/{chunk-T7PQB64I.js → chunk-FOCLZY64.js} +69 -3
- package/dist/chunk-FOCLZY64.js.map +1 -0
- package/dist/{chunk-YQDKOEUI.js → chunk-NAWUXVZQ.js} +43 -15
- package/dist/chunk-NAWUXVZQ.js.map +1 -0
- package/dist/{client-BJju2aT7.d.ts → client-CKtDWayV.d.ts} +1 -1
- package/dist/client.d.ts +2 -2
- package/dist/client.js +2 -1
- package/dist/dev-utils.d.ts +6 -2
- package/dist/dev-utils.js +3 -1
- package/dist/{entity-extensions-D09rvNgi.d.ts → entity-extensions-gJuSTXYZ.d.ts} +2 -2
- package/dist/globalization.d.ts +7 -2
- package/dist/globalization.js +11 -1
- package/dist/index.d.ts +42 -8
- package/dist/index.js +80 -18
- package/dist/index.js.map +1 -1
- package/dist/locale.d.ts +15 -0
- package/dist/locale.js +13 -0
- package/dist/locale.js.map +1 -0
- package/dist/{manifest-zSGjFCcJ.d.ts → manifest-CDTCsMYV.d.ts} +22 -4
- package/dist/react.d.ts +50 -3
- package/dist/react.js +105 -11
- package/dist/react.js.map +1 -1
- package/dist/server.d.ts +5 -4
- package/dist/trpc.d.ts +2 -1
- package/dist/{types-oPAs313y.d.ts → types-9v-WZ_NU.d.ts} +9 -0
- package/package.json +10 -1
- package/dist/chunk-46KUIGB6.js.map +0 -1
- package/dist/chunk-T7PQB64I.js.map +0 -1
- package/dist/chunk-YQDKOEUI.js.map +0 -1
package/dist/locale.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 {
|
|
3
|
-
import './
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
i18n
|
|
40
|
-
}
|
|
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
|
|
44
|
-
|
|
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
|
|
91
|
+
[i18n]
|
|
47
92
|
);
|
|
93
|
+
const tCommon = i18n.tCommon ?? fallbackPluginTranslate;
|
|
48
94
|
return React.useMemo(
|
|
49
|
-
() => ({
|
|
50
|
-
|
|
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":"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","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-
|
|
3
|
-
export { H as HookContext, c as HookTransaction } from './types-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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":[]}
|