talizen 0.2.11 → 0.2.13
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/README.md +4 -0
- package/core.d.ts +8 -0
- package/core.js +8 -0
- package/func-runtime.d.ts +8 -0
- package/func.d.ts +2 -2
- package/func.js +36 -7
- package/i18n.d.ts +4 -10
- package/i18n.js +31 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -216,6 +216,10 @@ export function list(input: { offset?: number }, ctx: TalizenFuncContext) {
|
|
|
216
216
|
|
|
217
217
|
`ctx.db`, `ctx.cache`, `ctx.auth`, `ctx.request`, and `ctx.cookies` are injected by the Talizen Func runtime. `talizen/func-runtime` is a type-only authoring module; do not import runtime values from it.
|
|
218
218
|
|
|
219
|
+
`ctx.request` exposes Fetch-style one-shot body readers. Use `await ctx.request.text()` when a webhook signature must be verified against the exact request bytes, `await ctx.request.json()` for parsed JSON, or `await ctx.request.arrayBuffer()` for binary input. Reading the body sets `ctx.request.bodyUsed`; a second read rejects. `ctx.response.status(code)` sets the actual HTTP response status (100-599), including statuses returned when a Func throws after setting the status. The runtime also provides `TextEncoder` and the HMAC SHA-256 subset of `crypto.subtle` (`importKey`, `sign`, and `verify`) for webhook verification.
|
|
220
|
+
|
|
221
|
+
Func HTTP responses use the HTTP status code rather than a top-level `ok` field. Successful responses contain `{ result: ... }`; failed responses contain `{ error: ... }`. `invoke()` unwraps `result` for callers.
|
|
222
|
+
|
|
219
223
|
## Package Layout
|
|
220
224
|
|
|
221
225
|
- `talizen/core`: shared runtime config, request helpers, and base data types.
|
package/core.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface TalizenClientConfig {
|
|
|
17
17
|
headers?: HeadersInit;
|
|
18
18
|
fetch?: typeof fetch;
|
|
19
19
|
onFileUploadProcess?: TalizenFileUploadProcessCallback;
|
|
20
|
+
i18n?: Partial<TalizenLocaleRuntime>;
|
|
21
|
+
messages?: Record<string, unknown>;
|
|
20
22
|
}
|
|
21
23
|
export interface TalizenRequestOptions extends TalizenClientConfig {
|
|
22
24
|
signal?: AbortSignal;
|
|
@@ -25,8 +27,14 @@ export interface TalizenErrorBody {
|
|
|
25
27
|
code?: number | string;
|
|
26
28
|
message?: string;
|
|
27
29
|
}
|
|
30
|
+
export interface TalizenLocaleRuntime {
|
|
31
|
+
locale: string;
|
|
32
|
+
locales: string[];
|
|
33
|
+
defaultLocale: string;
|
|
34
|
+
}
|
|
28
35
|
export declare function setTalizenConfig(config: TalizenClientConfig): void;
|
|
29
36
|
export declare function getTalizenConfig(): TalizenClientConfig;
|
|
37
|
+
export declare function subscribeTalizenConfig(listener: () => void): () => void;
|
|
30
38
|
declare global {
|
|
31
39
|
var TalizenConfig: TalizenClientConfig | undefined;
|
|
32
40
|
}
|
package/core.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
let talizenConfig = {};
|
|
2
|
+
const talizenConfigListeners = new Set();
|
|
2
3
|
export function setTalizenConfig(config) {
|
|
3
4
|
talizenConfig = {
|
|
4
5
|
...talizenConfig,
|
|
5
6
|
...config,
|
|
6
7
|
};
|
|
8
|
+
talizenConfigListeners.forEach(listener => listener());
|
|
7
9
|
}
|
|
8
10
|
export function getTalizenConfig() {
|
|
9
11
|
return talizenConfig;
|
|
10
12
|
}
|
|
13
|
+
export function subscribeTalizenConfig(listener) {
|
|
14
|
+
talizenConfigListeners.add(listener);
|
|
15
|
+
return () => {
|
|
16
|
+
talizenConfigListeners.delete(listener);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
11
19
|
export function resolveTalizenConfig(config) {
|
|
12
20
|
// globalThis is the global object in the browser and node.js
|
|
13
21
|
const globalTalizenConfig = typeof globalThis !== "undefined" ? globalThis.TalizenConfig : {};
|
package/func-runtime.d.ts
CHANGED
|
@@ -74,6 +74,13 @@ export interface FuncRequestRuntime {
|
|
|
74
74
|
path: string;
|
|
75
75
|
headers: FuncReadonlyStringMap;
|
|
76
76
|
cookies: FuncReadonlyStringMap;
|
|
77
|
+
readonly bodyUsed: boolean;
|
|
78
|
+
text(): Promise<string>;
|
|
79
|
+
json<T = unknown>(): Promise<T>;
|
|
80
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
81
|
+
}
|
|
82
|
+
export interface FuncResponseRuntime {
|
|
83
|
+
status(code: number): void;
|
|
77
84
|
}
|
|
78
85
|
export interface FuncCookieSetOptions {
|
|
79
86
|
path?: string;
|
|
@@ -96,6 +103,7 @@ export interface TalizenFuncContext {
|
|
|
96
103
|
trace_id: string;
|
|
97
104
|
extra?: Record<string, unknown>;
|
|
98
105
|
request: FuncRequestRuntime;
|
|
106
|
+
response: FuncResponseRuntime;
|
|
99
107
|
db: FuncDbRuntime;
|
|
100
108
|
auth: FuncAuthRuntime;
|
|
101
109
|
cache: FuncCacheRuntime;
|
package/func.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export interface FuncLogEntry {
|
|
|
4
4
|
text: string;
|
|
5
5
|
}
|
|
6
6
|
export interface FuncRunResponse<T = unknown> {
|
|
7
|
-
ok: boolean;
|
|
8
7
|
result?: T;
|
|
9
8
|
logs?: FuncLogEntry[];
|
|
10
9
|
error?: string;
|
|
@@ -13,7 +12,8 @@ export declare class TalizenFuncError extends Error {
|
|
|
13
12
|
readonly key: string;
|
|
14
13
|
readonly method: string;
|
|
15
14
|
readonly logs: FuncLogEntry[];
|
|
16
|
-
|
|
15
|
+
readonly status: number;
|
|
16
|
+
constructor(key: string, method: string, message: string, logs?: FuncLogEntry[], status?: number);
|
|
17
17
|
}
|
|
18
18
|
export declare function invoke<T = unknown>(name: string, input?: unknown, options?: RunFuncOptions): Promise<T>;
|
|
19
19
|
export interface RunFuncOptions extends TalizenRequestOptions {
|
package/func.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { requestJson, } from "./core.js";
|
|
1
|
+
import { requestJson, TalizenHttpError, } from "./core.js";
|
|
2
2
|
export class TalizenFuncError extends Error {
|
|
3
3
|
key;
|
|
4
4
|
method;
|
|
5
5
|
logs;
|
|
6
|
-
|
|
6
|
+
status;
|
|
7
|
+
constructor(key, method, message, logs, status = 200) {
|
|
7
8
|
super(message);
|
|
8
9
|
this.name = "TalizenFuncError";
|
|
9
10
|
this.key = key;
|
|
10
11
|
this.method = method;
|
|
11
12
|
this.logs = logs ?? [];
|
|
13
|
+
this.status = status;
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
export async function invoke(name, input, options) {
|
|
@@ -23,15 +25,42 @@ export async function runFunc(key, input, options) {
|
|
|
23
25
|
const method = normalizeFuncMethod(options?.method);
|
|
24
26
|
const timeoutMS = normalizeTimeoutMS(options?.timeoutMS ?? options?.timeoutMs);
|
|
25
27
|
const path = `/func/${encodeFuncKey(normalizedKey)}${method === "main" ? "" : `.${encodeURIComponent(method)}`}${timeoutMS ? `?timeout_ms=${timeoutMS}` : ""}`;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
let response;
|
|
29
|
+
try {
|
|
30
|
+
response = await requestJson(path, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: JSON.stringify(input ?? {}),
|
|
33
|
+
}, options);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof TalizenHttpError) {
|
|
37
|
+
throw new TalizenFuncError(normalizedKey, method, funcHttpErrorMessage(error), undefined, error.status);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
if (response.error) {
|
|
31
42
|
throw new TalizenFuncError(normalizedKey, method, response.error || "Talizen func failed.", response.logs);
|
|
32
43
|
}
|
|
33
44
|
return response.result;
|
|
34
45
|
}
|
|
46
|
+
function funcHttpErrorMessage(error) {
|
|
47
|
+
try {
|
|
48
|
+
const payload = JSON.parse(error.body);
|
|
49
|
+
if (typeof payload.error === "string") {
|
|
50
|
+
return payload.error;
|
|
51
|
+
}
|
|
52
|
+
if (payload.error && typeof payload.error === "object") {
|
|
53
|
+
const message = payload.error.message;
|
|
54
|
+
if (typeof message === "string") {
|
|
55
|
+
return message;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Fall through to the HTTP error body.
|
|
61
|
+
}
|
|
62
|
+
return error.body || error.message || "Talizen func failed.";
|
|
63
|
+
}
|
|
35
64
|
export function parseInvokeName(name) {
|
|
36
65
|
const normalized = normalizeFuncKey(name);
|
|
37
66
|
const lastSlash = normalized.lastIndexOf("/");
|
package/i18n.d.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { type TalizenLocaleRuntime } from "./core.js";
|
|
2
3
|
/**
|
|
3
|
-
* 当前语言运行时信息。由 Talizen
|
|
4
|
-
*
|
|
4
|
+
* 当前语言运行时信息。由 Talizen 渲染引擎写入 TalizenConfig.i18n
|
|
5
|
+
* (旧版 globalThis.__TALIZEN_I18N__ 仍作为兼容 fallback)。站点未开启多语言时为空。
|
|
5
6
|
*/
|
|
6
|
-
export
|
|
7
|
-
/** 当前生效语言;站点未配置多语言时为 ""。 */
|
|
8
|
-
locale: string;
|
|
9
|
-
/** 站点配置的全部语言。 */
|
|
10
|
-
locales: string[];
|
|
11
|
-
/** 默认语言(其 URL 无前缀)。 */
|
|
12
|
-
defaultLocale: string;
|
|
13
|
-
}
|
|
7
|
+
export type LocaleRuntime = TalizenLocaleRuntime;
|
|
14
8
|
declare global {
|
|
15
9
|
var __TALIZEN_I18N__: Partial<LocaleRuntime> | undefined;
|
|
16
10
|
}
|
package/i18n.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { getTalizenConfig, subscribeTalizenConfig } from "./core.js";
|
|
3
|
+
let lastSnapshot = null;
|
|
4
|
+
let lastSnapshotI18n;
|
|
5
|
+
let lastSnapshotMessages;
|
|
6
|
+
function readI18nRuntimeConfig() {
|
|
7
|
+
const config = getTalizenConfig();
|
|
8
|
+
const fallbackI18n = typeof globalThis !== "undefined" ? globalThis.__TALIZEN_I18N__ : undefined;
|
|
9
|
+
const fallbackMessages = typeof globalThis !== "undefined" ? globalThis.__TALIZEN_MESSAGES__ : undefined;
|
|
10
|
+
const i18n = config.i18n ?? fallbackI18n;
|
|
11
|
+
const messages = config.messages ?? fallbackMessages ?? {};
|
|
12
|
+
if (lastSnapshot && lastSnapshotI18n === i18n && lastSnapshotMessages === messages) {
|
|
13
|
+
return lastSnapshot;
|
|
14
|
+
}
|
|
15
|
+
lastSnapshotI18n = i18n;
|
|
16
|
+
lastSnapshotMessages = messages;
|
|
17
|
+
lastSnapshot = { i18n, messages };
|
|
18
|
+
return lastSnapshot;
|
|
19
|
+
}
|
|
20
|
+
function normalizeLocaleRuntime(value) {
|
|
21
|
+
const d = value ?? {};
|
|
4
22
|
const locales = Array.isArray(d.locales) ? d.locales.filter((l) => typeof l === "string") : [];
|
|
5
23
|
return {
|
|
6
24
|
locale: typeof d.locale === "string" ? d.locale : "",
|
|
@@ -8,6 +26,9 @@ function readLocaleRuntime() {
|
|
|
8
26
|
defaultLocale: typeof d.defaultLocale === "string" ? d.defaultLocale : (locales[0] ?? ""),
|
|
9
27
|
};
|
|
10
28
|
}
|
|
29
|
+
function readLocaleRuntime() {
|
|
30
|
+
return normalizeLocaleRuntime(readI18nRuntimeConfig().i18n);
|
|
31
|
+
}
|
|
11
32
|
/**
|
|
12
33
|
* 读取当前语言运行时信息(当前语言 / 全部语言 / 默认语言)。SSR 与客户端返回一致。
|
|
13
34
|
* 站点未开启多语言时 `locale === ""`。
|
|
@@ -17,7 +38,8 @@ function readLocaleRuntime() {
|
|
|
17
38
|
* ```
|
|
18
39
|
*/
|
|
19
40
|
export function useLocale() {
|
|
20
|
-
|
|
41
|
+
const snapshot = React.useSyncExternalStore(subscribeTalizenConfig, readI18nRuntimeConfig, readI18nRuntimeConfig);
|
|
42
|
+
return normalizeLocaleRuntime(snapshot.i18n);
|
|
21
43
|
}
|
|
22
44
|
/**
|
|
23
45
|
* 读取当前语言运行时信息——与 useLocale() 完全等价,但命名为 get*(**不是 hook**),
|
|
@@ -67,6 +89,7 @@ export const LOCALE_COOKIE = "CREGHT_LOCALE";
|
|
|
67
89
|
*/
|
|
68
90
|
export function Link(props) {
|
|
69
91
|
const { href, locale, onClick, ...rest } = props;
|
|
92
|
+
useLocale();
|
|
70
93
|
const finalHref = localizedPath(href, locale);
|
|
71
94
|
const handleClick = (event) => {
|
|
72
95
|
if (locale && typeof document !== "undefined") {
|
|
@@ -77,7 +100,7 @@ export function Link(props) {
|
|
|
77
100
|
return React.createElement("a", { ...rest, href: finalHref, onClick: handleClick });
|
|
78
101
|
}
|
|
79
102
|
function readMessages() {
|
|
80
|
-
return (
|
|
103
|
+
return readI18nRuntimeConfig().messages;
|
|
81
104
|
}
|
|
82
105
|
function lookupMessage(obj, path) {
|
|
83
106
|
let cur = obj;
|
|
@@ -99,8 +122,8 @@ function lookupMessage(obj, path) {
|
|
|
99
122
|
*
|
|
100
123
|
* UI chrome 用 useTranslations;文章等内容用 CMS 字段级 _i18n(见 listContents/getContent)。
|
|
101
124
|
*/
|
|
102
|
-
function buildTranslator(namespace) {
|
|
103
|
-
const messages = readMessages();
|
|
125
|
+
function buildTranslator(namespace, sourceMessages) {
|
|
126
|
+
const messages = sourceMessages ?? readMessages();
|
|
104
127
|
const scoped = namespace ? lookupMessage(messages, namespace) : messages;
|
|
105
128
|
const base = (scoped && typeof scoped === "object" ? scoped : {});
|
|
106
129
|
return (key, vars) => {
|
|
@@ -112,7 +135,8 @@ function buildTranslator(namespace) {
|
|
|
112
135
|
};
|
|
113
136
|
}
|
|
114
137
|
export function useTranslations(namespace) {
|
|
115
|
-
|
|
138
|
+
const snapshot = React.useSyncExternalStore(subscribeTalizenConfig, readI18nRuntimeConfig, readI18nRuntimeConfig);
|
|
139
|
+
return React.useMemo(() => buildTranslator(namespace, snapshot.messages), [namespace, snapshot]);
|
|
116
140
|
}
|
|
117
141
|
/**
|
|
118
142
|
* 读取 UI 文案翻译器——与 useTranslations() 等价,但命名为 get*(**不是 hook**),
|