akanjs 2.3.9-rc.3 → 2.3.9-rc.4
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/common/Logger.ts +81 -1
- package/common/index.ts +8 -1
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +1 -1
- package/server/rscWorker.tsx +115 -26
- package/server/rscWorkerHost.ts +56 -12
- package/server/ssrFromRscRenderer.tsx +22 -5
- package/server/ssrTypes.ts +1 -0
- package/server/webRouter.ts +130 -12
- package/types/common/Logger.d.ts +20 -0
- package/types/common/index.d.ts +1 -1
- package/types/server/rscWorkerHost.d.ts +17 -2
- package/types/server/ssrTypes.d.ts +4 -0
package/common/Logger.ts
CHANGED
|
@@ -2,12 +2,24 @@ import dayjs from "dayjs";
|
|
|
2
2
|
|
|
3
3
|
const logLevels = ["trace", "verbose", "debug", "log", "info", "warn", "error"] as const;
|
|
4
4
|
export type LogLevel = (typeof logLevels)[number];
|
|
5
|
+
export type LoggerMeta = Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
export interface LoggerSerializedError {
|
|
8
|
+
name?: string;
|
|
9
|
+
message: string;
|
|
10
|
+
stack?: string;
|
|
11
|
+
cause?: LoggerSerializedError | string;
|
|
12
|
+
digest?: string;
|
|
13
|
+
code?: string | number;
|
|
14
|
+
status?: number;
|
|
15
|
+
}
|
|
5
16
|
|
|
6
17
|
export interface LoggerSinkEntry {
|
|
7
18
|
stream: "stdout" | "stderr";
|
|
8
19
|
level?: LogLevel;
|
|
9
20
|
message: string;
|
|
10
21
|
plainMessage: string;
|
|
22
|
+
meta?: LoggerMeta;
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
export type LoggerSink = (entry: LoggerSinkEntry) => void | Promise<void>;
|
|
@@ -33,6 +45,11 @@ const colorizeMap: { [key in LogLevel]: (text: string) => string } = {
|
|
|
33
45
|
|
|
34
46
|
const ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
35
47
|
|
|
48
|
+
function readErrorProp(error: unknown, key: string): unknown {
|
|
49
|
+
if (typeof error !== "object" || error === null || !(key in error)) return undefined;
|
|
50
|
+
return (error as Record<string, unknown>)[key];
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
/** Log-level aware logger used by Akan runtime, CLI, and application services. */
|
|
37
54
|
export class Logger {
|
|
38
55
|
static level: LogLevel = (process.env?.AKAN_PUBLIC_LOG_LEVEL as LogLevel | undefined) ?? "log";
|
|
@@ -86,6 +103,9 @@ export class Logger {
|
|
|
86
103
|
error(msg: string, context = "", name = this.name ?? "App") {
|
|
87
104
|
if (Logger.#shouldLog("error")) Logger.#printMessages(name, msg, context, "error");
|
|
88
105
|
}
|
|
106
|
+
errorObject(msg: string, error: unknown, meta: LoggerMeta = {}, context = "", name = this.name ?? "App") {
|
|
107
|
+
if (Logger.#shouldLog("error")) Logger.#printError(name, msg, error, meta, context);
|
|
108
|
+
}
|
|
89
109
|
raw(msg: string, method?: "console" | "process") {
|
|
90
110
|
Logger.rawLog(msg, method);
|
|
91
111
|
}
|
|
@@ -114,6 +134,60 @@ export class Logger {
|
|
|
114
134
|
static error(msg: string, context = "", name = "App") {
|
|
115
135
|
if (Logger.#shouldLog("error")) Logger.#printMessages(name, msg, context, "error");
|
|
116
136
|
}
|
|
137
|
+
static errorObject(msg: string, error: unknown, meta: LoggerMeta = {}, context = "", name = "App") {
|
|
138
|
+
if (Logger.#shouldLog("error")) Logger.#printError(name, msg, error, meta, context);
|
|
139
|
+
}
|
|
140
|
+
static createErrorId(input: { requestId?: string; scope?: string; sequence?: string | number } = {}) {
|
|
141
|
+
const parts = ["akanerr", input.requestId, input.scope, input.sequence, Date.now().toString(36)]
|
|
142
|
+
.filter((part): part is string | number => part !== undefined && part !== "")
|
|
143
|
+
.map((part) => String(part).replace(/[^a-zA-Z0-9_-]/g, "-"));
|
|
144
|
+
return parts.join("-");
|
|
145
|
+
}
|
|
146
|
+
static serializeError(error: unknown): LoggerSerializedError {
|
|
147
|
+
if (error instanceof Error) {
|
|
148
|
+
const digest = readErrorProp(error, "digest");
|
|
149
|
+
const code = readErrorProp(error, "code");
|
|
150
|
+
const status = readErrorProp(error, "status") ?? readErrorProp(error, "statusCode");
|
|
151
|
+
return {
|
|
152
|
+
name: error.name,
|
|
153
|
+
message: error.message,
|
|
154
|
+
...(error.stack ? { stack: error.stack } : {}),
|
|
155
|
+
...(error.cause !== undefined ? { cause: Logger.serializeError(error.cause) } : {}),
|
|
156
|
+
...(typeof digest === "string" ? { digest } : {}),
|
|
157
|
+
...(typeof code === "string" || typeof code === "number" ? { code } : {}),
|
|
158
|
+
...(typeof status === "number" ? { status } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (typeof error === "object" && error !== null) {
|
|
162
|
+
const message = readErrorProp(error, "message");
|
|
163
|
+
const name = readErrorProp(error, "name");
|
|
164
|
+
const stack = readErrorProp(error, "stack");
|
|
165
|
+
const cause = readErrorProp(error, "cause");
|
|
166
|
+
const digest = readErrorProp(error, "digest");
|
|
167
|
+
const code = readErrorProp(error, "code");
|
|
168
|
+
const status = readErrorProp(error, "status") ?? readErrorProp(error, "statusCode");
|
|
169
|
+
return {
|
|
170
|
+
...(typeof name === "string" ? { name } : {}),
|
|
171
|
+
message: typeof message === "string" ? message : String(error),
|
|
172
|
+
...(typeof stack === "string" ? { stack } : {}),
|
|
173
|
+
...(cause !== undefined ? { cause: Logger.serializeError(cause) } : {}),
|
|
174
|
+
...(typeof digest === "string" ? { digest } : {}),
|
|
175
|
+
...(typeof code === "string" || typeof code === "number" ? { code } : {}),
|
|
176
|
+
...(typeof status === "number" ? { status } : {}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return { message: String(error) };
|
|
180
|
+
}
|
|
181
|
+
static formatError(error: unknown) {
|
|
182
|
+
const serialized = Logger.serializeError(error);
|
|
183
|
+
const details = serialized.stack ?? serialized.message;
|
|
184
|
+
if (!serialized.cause) return details;
|
|
185
|
+
const cause =
|
|
186
|
+
typeof serialized.cause === "string"
|
|
187
|
+
? serialized.cause
|
|
188
|
+
: (serialized.cause.stack ?? serialized.cause.message);
|
|
189
|
+
return `${details}\nCaused by: ${cause}`;
|
|
190
|
+
}
|
|
117
191
|
static #colorize(msg: string, logLevel: LogLevel) {
|
|
118
192
|
return colorizeMap[logLevel](msg);
|
|
119
193
|
}
|
|
@@ -137,12 +211,18 @@ export class Logger {
|
|
|
137
211
|
}
|
|
138
212
|
}
|
|
139
213
|
}
|
|
214
|
+
static #printError(name: string | undefined, msg: string, error: unknown, meta: LoggerMeta, context: string) {
|
|
215
|
+
const serializedError = Logger.serializeError(error);
|
|
216
|
+
const message = `${msg}: ${Logger.formatError(error)}`;
|
|
217
|
+
Logger.#printMessages(name, message, context, "error", "stderr", { ...meta, error: serializedError });
|
|
218
|
+
}
|
|
140
219
|
static #printMessages(
|
|
141
220
|
name: string | undefined,
|
|
142
221
|
content: string,
|
|
143
222
|
context: string,
|
|
144
223
|
logLevel: LogLevel,
|
|
145
224
|
writeStreamType: "stdout" | "stderr" = logLevel === "error" ? "stderr" : "stdout",
|
|
225
|
+
meta?: LoggerMeta,
|
|
146
226
|
) {
|
|
147
227
|
const now = dayjs();
|
|
148
228
|
const replicaIdx = (process as unknown as NodeJS.Process | undefined)?.env?.AKAN_REPLICA_IDX;
|
|
@@ -158,7 +238,7 @@ export class Logger {
|
|
|
158
238
|
const timeDiffMsg = clc.yellow(`+${now.diff(Logger.#startAt, "ms")}ms`);
|
|
159
239
|
const message = `${processMsg} ${timestampMsg} ${logLevelMsg} ${contextMsg} ${contentMsg} ${timeDiffMsg}\n`;
|
|
160
240
|
if (Logger.#shouldEmitSink(logLevel))
|
|
161
|
-
Logger.#emit({ stream: writeStreamType, level: logLevel, message, plainMessage: Logger.#stripAnsi(message) });
|
|
241
|
+
Logger.#emit({ stream: writeStreamType, level: logLevel, message, plainMessage: Logger.#stripAnsi(message), meta });
|
|
162
242
|
if (!Logger.#shouldWriteConsole(logLevel)) return;
|
|
163
243
|
if (typeof window === "undefined")
|
|
164
244
|
(process[writeStreamType] as unknown as NodeJS.WriteStream | undefined)?.write(message);
|
package/common/index.ts
CHANGED
|
@@ -17,7 +17,14 @@ export { isPhoneNumber } from "./isPhoneNumber";
|
|
|
17
17
|
export { isQueryEqual } from "./isQueryEqual";
|
|
18
18
|
export { isValidDate } from "./isValidDate";
|
|
19
19
|
export { decodeJwtPayload } from "./jwtDecode";
|
|
20
|
-
export {
|
|
20
|
+
export {
|
|
21
|
+
Logger,
|
|
22
|
+
type LoggerMeta,
|
|
23
|
+
type LoggerSerializedError,
|
|
24
|
+
type LoggerSink,
|
|
25
|
+
type LoggerSinkEntry,
|
|
26
|
+
type LogLevel,
|
|
27
|
+
} from "./Logger";
|
|
21
28
|
export {
|
|
22
29
|
type AkanI18nConfig,
|
|
23
30
|
type AkanI18nConfigInput,
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/server/rscWorker.tsx
CHANGED
|
@@ -110,7 +110,7 @@ type InMsg = InitMsg | RenderMsg | CancelMsg | ReloadMsg | UpdateCssAssetsMsg |
|
|
|
110
110
|
type RenderControl =
|
|
111
111
|
| { type: "redirect"; location: string; method: "replace" | "push"; status: RedirectStatus }
|
|
112
112
|
| { type: "not-found" }
|
|
113
|
-
| { type: "error"; error: unknown };
|
|
113
|
+
| { type: "error"; error: unknown; errorId: string; digest: string };
|
|
114
114
|
interface FlightRenderResult {
|
|
115
115
|
chunks: Uint8Array[];
|
|
116
116
|
bytes: number;
|
|
@@ -206,6 +206,7 @@ export class RscRenderer {
|
|
|
206
206
|
#resultCacheHits = 0;
|
|
207
207
|
#resultCacheMisses = 0;
|
|
208
208
|
#resultCacheBypass = 0;
|
|
209
|
+
#renderErrorSequence = 0;
|
|
209
210
|
readonly #send: (message: unknown) => void;
|
|
210
211
|
|
|
211
212
|
constructor() {
|
|
@@ -223,6 +224,74 @@ export class RscRenderer {
|
|
|
223
224
|
this.#send({ type: "hello" });
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
#nextRenderErrorId(requestId: string | undefined, scope: string): string {
|
|
228
|
+
this.#renderErrorSequence += 1;
|
|
229
|
+
return Logger.createErrorId({ requestId, scope, sequence: this.#renderErrorSequence });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#createRenderErrorPayload(
|
|
233
|
+
error: unknown,
|
|
234
|
+
input: {
|
|
235
|
+
requestId?: string;
|
|
236
|
+
scope: string;
|
|
237
|
+
errorId?: string;
|
|
238
|
+
url?: URL | string;
|
|
239
|
+
pathname?: string;
|
|
240
|
+
routeId?: string;
|
|
241
|
+
trace?: RscTraceMetadata;
|
|
242
|
+
},
|
|
243
|
+
) {
|
|
244
|
+
const errorId = input.errorId ?? this.#nextRenderErrorId(input.requestId, input.scope);
|
|
245
|
+
const serialized = Logger.serializeError(error);
|
|
246
|
+
const pathname = input.pathname ?? input.trace?.pathname;
|
|
247
|
+
const routeId = input.routeId ?? input.trace?.routeId;
|
|
248
|
+
const url = typeof input.url === "string" ? input.url : input.url?.href;
|
|
249
|
+
const meta = {
|
|
250
|
+
errorId,
|
|
251
|
+
requestId: input.requestId,
|
|
252
|
+
scope: input.scope,
|
|
253
|
+
url,
|
|
254
|
+
pathname,
|
|
255
|
+
routeId,
|
|
256
|
+
digest: errorId,
|
|
257
|
+
originalDigest: serialized.digest,
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
message: serialized.message,
|
|
261
|
+
name: serialized.name,
|
|
262
|
+
stack: serialized.stack,
|
|
263
|
+
errorId,
|
|
264
|
+
digest: errorId,
|
|
265
|
+
requestId: input.requestId,
|
|
266
|
+
scope: input.scope,
|
|
267
|
+
pathname,
|
|
268
|
+
routeId,
|
|
269
|
+
meta,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#logRenderError(
|
|
274
|
+
message: string,
|
|
275
|
+
error: unknown,
|
|
276
|
+
input: {
|
|
277
|
+
requestId?: string;
|
|
278
|
+
scope: string;
|
|
279
|
+
errorId?: string;
|
|
280
|
+
url?: URL | string;
|
|
281
|
+
pathname?: string;
|
|
282
|
+
routeId?: string;
|
|
283
|
+
trace?: RscTraceMetadata;
|
|
284
|
+
},
|
|
285
|
+
) {
|
|
286
|
+
const payload = this.#createRenderErrorPayload(error, input);
|
|
287
|
+
this.#logger.errorObject(
|
|
288
|
+
`${message} requestId=${input.requestId ?? "(none)"} scope=${input.scope} errorId=${payload.errorId}`,
|
|
289
|
+
error,
|
|
290
|
+
payload.meta,
|
|
291
|
+
);
|
|
292
|
+
return payload;
|
|
293
|
+
}
|
|
294
|
+
|
|
226
295
|
#handleMessage(msg: InMsg): void {
|
|
227
296
|
switch (msg.type) {
|
|
228
297
|
case "init":
|
|
@@ -287,11 +356,11 @@ export class RscRenderer {
|
|
|
287
356
|
this.#logger.verbose(`init complete in ${Date.now() - startedAt}ms`);
|
|
288
357
|
this.#send({ type: "ready" });
|
|
289
358
|
} catch (error) {
|
|
290
|
-
this.#
|
|
359
|
+
const payload = this.#logRenderError("init failed", error, { requestId: "__init__", scope: "rsc-init" });
|
|
291
360
|
this.#send({
|
|
292
361
|
type: "error",
|
|
362
|
+
...payload,
|
|
293
363
|
requestId: "__init__",
|
|
294
|
-
message: error instanceof Error ? error.message : String(error),
|
|
295
364
|
});
|
|
296
365
|
}
|
|
297
366
|
}
|
|
@@ -326,14 +395,15 @@ export class RscRenderer {
|
|
|
326
395
|
this.#logger.verbose(`reload complete buildId=${msg.buildId} in ${Date.now() - startedAt}ms`);
|
|
327
396
|
this.#send({ type: "reloaded", buildId: msg.buildId });
|
|
328
397
|
} catch (error) {
|
|
329
|
-
this.#
|
|
330
|
-
|
|
331
|
-
|
|
398
|
+
const payload = this.#logRenderError(`reload failed buildId=${msg.buildId}`, error, {
|
|
399
|
+
requestId: "__reload__",
|
|
400
|
+
scope: "rsc-reload",
|
|
401
|
+
});
|
|
332
402
|
this.#send({
|
|
333
403
|
type: "error",
|
|
404
|
+
...payload,
|
|
334
405
|
requestId: "__reload__",
|
|
335
406
|
buildId: msg.buildId,
|
|
336
|
-
message: error instanceof Error ? error.message : String(error),
|
|
337
407
|
});
|
|
338
408
|
}
|
|
339
409
|
}
|
|
@@ -738,9 +808,13 @@ export class RscRenderer {
|
|
|
738
808
|
this.#send({ type: "not-found", requestId });
|
|
739
809
|
return;
|
|
740
810
|
}
|
|
741
|
-
this.#
|
|
742
|
-
|
|
743
|
-
|
|
811
|
+
const payload = this.#logRenderError("[RSC] worker render failed", error, {
|
|
812
|
+
requestId,
|
|
813
|
+
scope: "rsc-render",
|
|
814
|
+
url,
|
|
815
|
+
pathname: activeRoute.url?.pathname,
|
|
816
|
+
routeId: activeRoute.match?.pathRoute.path,
|
|
817
|
+
});
|
|
744
818
|
const fallbackUrl = activeRoute.url;
|
|
745
819
|
const fallbackMatch = activeRoute.match;
|
|
746
820
|
if (
|
|
@@ -762,8 +836,8 @@ export class RscRenderer {
|
|
|
762
836
|
}
|
|
763
837
|
this.#send({
|
|
764
838
|
type: "error",
|
|
839
|
+
...payload,
|
|
765
840
|
requestId,
|
|
766
|
-
message: error instanceof Error ? error.message : String(error),
|
|
767
841
|
});
|
|
768
842
|
} finally {
|
|
769
843
|
this.#activeRenderReaders.delete(requestId);
|
|
@@ -834,6 +908,7 @@ export class RscRenderer {
|
|
|
834
908
|
const controlRef: { current: RenderControl | null } = { current: null };
|
|
835
909
|
const stream = await renderToReadableStream(element, clientManifest, {
|
|
836
910
|
onError: (error) => {
|
|
911
|
+
if (controlRef.current?.type === "error") return controlRef.current.digest;
|
|
837
912
|
if (isAkanRedirectError(error)) {
|
|
838
913
|
controlRef.current = {
|
|
839
914
|
type: "redirect",
|
|
@@ -851,8 +926,13 @@ export class RscRenderer {
|
|
|
851
926
|
controlRef.current = { type: "not-found" };
|
|
852
927
|
return error.digest;
|
|
853
928
|
}
|
|
854
|
-
|
|
855
|
-
|
|
929
|
+
const payload = this.#logRenderError("[RSC] worker render stream error", error, {
|
|
930
|
+
requestId: options.requestId,
|
|
931
|
+
scope: "rsc-stream",
|
|
932
|
+
trace: options.trace,
|
|
933
|
+
});
|
|
934
|
+
controlRef.current = { type: "error", error, errorId: payload.errorId, digest: payload.digest };
|
|
935
|
+
return payload.digest;
|
|
856
936
|
},
|
|
857
937
|
});
|
|
858
938
|
const reader = stream.getReader();
|
|
@@ -991,11 +1071,14 @@ export class RscRenderer {
|
|
|
991
1071
|
this.#stats.totalFlightChunks += result.chunksCount;
|
|
992
1072
|
return true;
|
|
993
1073
|
} catch (fallbackError) {
|
|
994
|
-
this.#
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1074
|
+
this.#logRenderError(`render[${requestId}] custom ${kind} fallback failed`, fallbackError, {
|
|
1075
|
+
requestId,
|
|
1076
|
+
scope: `fallback:${kind}`,
|
|
1077
|
+
url,
|
|
1078
|
+
pathname,
|
|
1079
|
+
routeId: route.path,
|
|
1080
|
+
trace,
|
|
1081
|
+
});
|
|
999
1082
|
return false;
|
|
1000
1083
|
}
|
|
1001
1084
|
}
|
|
@@ -1025,11 +1108,13 @@ export class RscRenderer {
|
|
|
1025
1108
|
this.#stats.totalFlightChunks += result.chunksCount;
|
|
1026
1109
|
return true;
|
|
1027
1110
|
} catch (error) {
|
|
1028
|
-
this.#
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1111
|
+
this.#logRenderError(`render[${requestId}] system not-found fallback failed`, error, {
|
|
1112
|
+
requestId,
|
|
1113
|
+
scope: "fallback:system-not-found",
|
|
1114
|
+
url,
|
|
1115
|
+
pathname: url.pathname,
|
|
1116
|
+
trace,
|
|
1117
|
+
});
|
|
1033
1118
|
return false;
|
|
1034
1119
|
}
|
|
1035
1120
|
}
|
|
@@ -1047,9 +1132,13 @@ export class RscRenderer {
|
|
|
1047
1132
|
return;
|
|
1048
1133
|
}
|
|
1049
1134
|
if (control.type === "error") {
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1135
|
+
const payload = this.#createRenderErrorPayload(control.error, {
|
|
1136
|
+
requestId,
|
|
1137
|
+
scope: "rsc-render-control",
|
|
1138
|
+
errorId: control.errorId,
|
|
1139
|
+
});
|
|
1140
|
+
this.#logger.verbose(`render[${requestId}] error errorId=${payload.errorId}`);
|
|
1141
|
+
this.#send({ type: "error", ...payload, requestId });
|
|
1053
1142
|
return;
|
|
1054
1143
|
}
|
|
1055
1144
|
this.#logger.verbose(`render[${requestId}] not-found`);
|
package/server/rscWorkerHost.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { type AkanI18nConfig, DEFAULT_AKAN_I18N, Logger } from "akanjs/common";
|
|
4
|
+
import { type AkanI18nConfig, DEFAULT_AKAN_I18N, Logger, type LoggerMeta } from "akanjs/common";
|
|
5
5
|
import type { AkanTheme } from "akanjs/fetch";
|
|
6
6
|
import type { AkanMetricsReport } from "akanjs/service";
|
|
7
7
|
import type { ClientManifest } from "./artifact";
|
|
@@ -14,7 +14,7 @@ const DEFAULT_RSC_HOST_MAX_PENDING_CHUNKS = 256;
|
|
|
14
14
|
export interface RscPending {
|
|
15
15
|
onChunk: (data: Uint8Array) => void;
|
|
16
16
|
onEnd: () => void;
|
|
17
|
-
onError: (
|
|
17
|
+
onError: (error: string | RscWorkerErrorPayload) => void;
|
|
18
18
|
onMeta?: (meta: { theme?: AkanTheme; status?: number; trace?: RscTraceMetadata }) => void;
|
|
19
19
|
onCacheState?: (state: RouteCacheRenderState) => void;
|
|
20
20
|
onRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
|
|
@@ -25,6 +25,19 @@ export interface RscPending {
|
|
|
25
25
|
export type RscRedirectMethod = "replace" | "push";
|
|
26
26
|
export type RscRedirectStatus = 303 | 307 | 308;
|
|
27
27
|
|
|
28
|
+
export interface RscWorkerErrorPayload {
|
|
29
|
+
message: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
stack?: string;
|
|
32
|
+
errorId?: string;
|
|
33
|
+
digest?: string;
|
|
34
|
+
requestId?: string;
|
|
35
|
+
scope?: string;
|
|
36
|
+
pathname?: string;
|
|
37
|
+
routeId?: string;
|
|
38
|
+
meta?: LoggerMeta;
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
export interface RscWorkerInvalidateCacheMessage {
|
|
29
42
|
type: "invalidate-cache";
|
|
30
43
|
reason?: string;
|
|
@@ -35,6 +48,7 @@ export interface RscWorkerInvalidateCacheMessage {
|
|
|
35
48
|
export type RscRenderResult =
|
|
36
49
|
| {
|
|
37
50
|
type: "stream";
|
|
51
|
+
requestId?: string;
|
|
38
52
|
stream: ReadableStream<Uint8Array>;
|
|
39
53
|
theme?: AkanTheme;
|
|
40
54
|
status?: number;
|
|
@@ -66,6 +80,23 @@ function createRscRenderAbortError(reason?: unknown): Error {
|
|
|
66
80
|
return error;
|
|
67
81
|
}
|
|
68
82
|
|
|
83
|
+
export function createRscWorkerError(input: string | RscWorkerErrorPayload): Error {
|
|
84
|
+
if (typeof input === "string") return new Error(input);
|
|
85
|
+
const error = new Error(input.message);
|
|
86
|
+
if (input.name) error.name = input.name;
|
|
87
|
+
if (input.stack) error.stack = input.stack;
|
|
88
|
+
Object.assign(error, {
|
|
89
|
+
errorId: input.errorId,
|
|
90
|
+
digest: input.digest ?? input.errorId,
|
|
91
|
+
requestId: input.requestId,
|
|
92
|
+
scope: input.scope,
|
|
93
|
+
pathname: input.pathname,
|
|
94
|
+
routeId: input.routeId,
|
|
95
|
+
meta: input.meta,
|
|
96
|
+
});
|
|
97
|
+
return error;
|
|
98
|
+
}
|
|
99
|
+
|
|
69
100
|
export function createIdempotentRscRenderCancel(onCancel: (reason?: unknown) => void): (reason?: unknown) => void {
|
|
70
101
|
let cancelled = false;
|
|
71
102
|
return (reason?: unknown) => {
|
|
@@ -89,6 +120,7 @@ export function createRscWorkerInvalidateCacheMessage(
|
|
|
89
120
|
}
|
|
90
121
|
|
|
91
122
|
export function createRscHostRenderStream(input: {
|
|
123
|
+
requestId?: string;
|
|
92
124
|
setPending: (pending: RscPending) => void;
|
|
93
125
|
deletePending: () => void;
|
|
94
126
|
sendRenderOrQueue: () => void;
|
|
@@ -153,7 +185,17 @@ export function createRscHostRenderStream(input: {
|
|
|
153
185
|
const settleStream = () => {
|
|
154
186
|
if (settled) return;
|
|
155
187
|
settled = true;
|
|
156
|
-
|
|
188
|
+
resolve({
|
|
189
|
+
type: "stream",
|
|
190
|
+
requestId: input.requestId,
|
|
191
|
+
stream,
|
|
192
|
+
theme,
|
|
193
|
+
status,
|
|
194
|
+
trace,
|
|
195
|
+
lateControl,
|
|
196
|
+
cacheState,
|
|
197
|
+
cancel: cancelRender,
|
|
198
|
+
});
|
|
157
199
|
};
|
|
158
200
|
input.setPending({
|
|
159
201
|
onMeta: (meta) => {
|
|
@@ -182,16 +224,17 @@ export function createRscHostRenderStream(input: {
|
|
|
182
224
|
settleStream();
|
|
183
225
|
controller.close();
|
|
184
226
|
},
|
|
185
|
-
onError: (
|
|
227
|
+
onError: (payload) => {
|
|
228
|
+
const error = createRscWorkerError(payload);
|
|
186
229
|
settleLateControl(null);
|
|
187
230
|
settleCacheState({ cacheable: false, reason: "error" });
|
|
188
231
|
cleanupAbortListener();
|
|
189
232
|
if (!settled) {
|
|
190
233
|
settled = true;
|
|
191
|
-
reject(
|
|
234
|
+
reject(error);
|
|
192
235
|
return;
|
|
193
236
|
}
|
|
194
|
-
controller.error(
|
|
237
|
+
controller.error(error);
|
|
195
238
|
},
|
|
196
239
|
onRedirect: (location, method, status) => {
|
|
197
240
|
settleLateControl(null);
|
|
@@ -262,7 +305,7 @@ type RscInMsg =
|
|
|
262
305
|
}
|
|
263
306
|
| { type: "not-found"; requestId: string }
|
|
264
307
|
| { type: "metrics"; metrics: AkanMetricsReport }
|
|
265
|
-
| { type: "error"; requestId: string;
|
|
308
|
+
| ({ type: "error"; requestId: string; buildId?: number } & RscWorkerErrorPayload);
|
|
266
309
|
|
|
267
310
|
export interface RscWorkerReloadInput {
|
|
268
311
|
clientManifest: ClientManifest;
|
|
@@ -379,7 +422,7 @@ export class RscWorker {
|
|
|
379
422
|
this.#pending.set(requestId, {
|
|
380
423
|
onChunk: (data) => controller.enqueue(data),
|
|
381
424
|
onEnd: () => controller.close(),
|
|
382
|
-
onError: (
|
|
425
|
+
onError: (payload) => controller.error(createRscWorkerError(payload)),
|
|
383
426
|
});
|
|
384
427
|
const send = () => {
|
|
385
428
|
|
|
@@ -412,6 +455,7 @@ export class RscWorker {
|
|
|
412
455
|
): Promise<RscRenderResult> {
|
|
413
456
|
const requestId = crypto.randomUUID();
|
|
414
457
|
return createRscHostRenderStream({
|
|
458
|
+
requestId,
|
|
415
459
|
setPending: (pending) => {
|
|
416
460
|
this.#pending.set(requestId, pending);
|
|
417
461
|
},
|
|
@@ -656,8 +700,8 @@ export class RscWorker {
|
|
|
656
700
|
case "error":
|
|
657
701
|
if (message.requestId === "__init__") {
|
|
658
702
|
|
|
659
|
-
if (!this.#readyResolved) this.#rejectReady(
|
|
660
|
-
else this.#logger.
|
|
703
|
+
if (!this.#readyResolved) this.#rejectReady(createRscWorkerError(message));
|
|
704
|
+
else this.#logger.errorObject("[rsc] worker init error on restart", createRscWorkerError(message), message.meta);
|
|
661
705
|
return;
|
|
662
706
|
}
|
|
663
707
|
if (message.requestId === "__reload__") {
|
|
@@ -665,12 +709,12 @@ export class RscWorker {
|
|
|
665
709
|
this.#pendingReload &&
|
|
666
710
|
(message.buildId === undefined || this.#pendingReload.targetBuildId === message.buildId)
|
|
667
711
|
) {
|
|
668
|
-
this.#pendingReload.reject(
|
|
712
|
+
this.#pendingReload.reject(createRscWorkerError(message));
|
|
669
713
|
this.#pendingReload = null;
|
|
670
714
|
}
|
|
671
715
|
return;
|
|
672
716
|
}
|
|
673
|
-
this.#resolvePending(message.requestId, (p) => p.onError(
|
|
717
|
+
this.#resolvePending(message.requestId, (p) => p.onError(message));
|
|
674
718
|
return;
|
|
675
719
|
}
|
|
676
720
|
}
|
|
@@ -595,11 +595,28 @@ export class SsrFromRscRenderer {
|
|
|
595
595
|
: SsrFromRscRenderer.#clientBootstrap;
|
|
596
596
|
|
|
597
597
|
const renderHtml = async () => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
})
|
|
602
|
-
|
|
598
|
+
let root: ReactNode;
|
|
599
|
+
try {
|
|
600
|
+
root = await thenable;
|
|
601
|
+
} catch (error) {
|
|
602
|
+
input.onError?.({ scope: "flight-decode", error });
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
let stream: ReadableStream<Uint8Array> & { allReady: Promise<void> };
|
|
606
|
+
try {
|
|
607
|
+
stream = await renderToReadableStream(root, {
|
|
608
|
+
bootstrapScriptContent: bootstrap,
|
|
609
|
+
});
|
|
610
|
+
} catch (error) {
|
|
611
|
+
input.onError?.({ scope: "html-render", error });
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
614
|
+
try {
|
|
615
|
+
await stream.allReady;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
input.onError?.({ scope: "html-all-ready", error });
|
|
618
|
+
throw error;
|
|
619
|
+
}
|
|
603
620
|
return stream;
|
|
604
621
|
};
|
|
605
622
|
const requestContext = input.requestStore ?? input.request;
|
package/server/ssrTypes.ts
CHANGED
|
@@ -73,4 +73,5 @@ export interface SsrFromRscInput {
|
|
|
73
73
|
injectThemeInitScript?: boolean;
|
|
74
74
|
lateControl?: Promise<SsrLateRedirect | null>;
|
|
75
75
|
onCancel?: (reason?: unknown) => void;
|
|
76
|
+
onError?: (event: { scope: "flight-decode" | "html-render" | "html-all-ready"; error: unknown }) => void;
|
|
76
77
|
}
|
package/server/webRouter.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_AKAN_I18N,
|
|
7
7
|
getBasePathFromPathname,
|
|
8
8
|
Logger,
|
|
9
|
+
type LoggerMeta,
|
|
9
10
|
parseAkanI18nEnv,
|
|
10
11
|
} from "akanjs/common";
|
|
11
12
|
import { type AkanRequestStore, createRequestStore, parseCookieHeader } from "akanjs/fetch";
|
|
@@ -57,6 +58,15 @@ const RESERVED_BASE_PATHS = new Set(["admin"]);
|
|
|
57
58
|
const CLIENT_CLOSED_REQUEST_STATUS = 499;
|
|
58
59
|
export const DEFAULT_HTML_RESULT_CACHE_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
59
60
|
|
|
61
|
+
interface SsrErrorLogMeta extends LoggerMeta {
|
|
62
|
+
errorId: string;
|
|
63
|
+
requestId?: string;
|
|
64
|
+
scope: string;
|
|
65
|
+
pathname?: string;
|
|
66
|
+
routeId?: string;
|
|
67
|
+
url?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
export function createRscRedirectResponse(
|
|
61
71
|
location: string,
|
|
62
72
|
method: RscRedirectMethod,
|
|
@@ -449,10 +459,16 @@ export class WebRouter {
|
|
|
449
459
|
return createRscRedirectResponse(result.location, result.method, result.status);
|
|
450
460
|
if (result.type === "not-found") return WebRouter.#rscNotFoundResponse();
|
|
451
461
|
if (result.status && result.status >= 500)
|
|
452
|
-
return this.#renderRscErrorResponse(
|
|
462
|
+
return this.#renderRscErrorResponse(
|
|
463
|
+
"__rsc",
|
|
464
|
+
new Error("RSC render returned an error status"),
|
|
465
|
+
req,
|
|
466
|
+
result.trace,
|
|
467
|
+
result.requestId,
|
|
468
|
+
);
|
|
453
469
|
return createRscNavigationStreamResponse(result);
|
|
454
470
|
} catch (err) {
|
|
455
|
-
return this.#renderRscErrorResponse("__rsc", err);
|
|
471
|
+
return this.#renderRscErrorResponse("__rsc", err, req);
|
|
456
472
|
}
|
|
457
473
|
},
|
|
458
474
|
"/__rsc/manifest": () =>
|
|
@@ -530,6 +546,8 @@ export class WebRouter {
|
|
|
530
546
|
);
|
|
531
547
|
}
|
|
532
548
|
|
|
549
|
+
let activeRscRequestId: string | undefined;
|
|
550
|
+
let activeRscTrace: RscTraceMetadata | undefined;
|
|
533
551
|
try {
|
|
534
552
|
this.#requestStats.fullSsr += 1;
|
|
535
553
|
const manifest = await this.#ensureRoute(url);
|
|
@@ -548,6 +566,10 @@ export class WebRouter {
|
|
|
548
566
|
clientManifest: manifest.clientManifest,
|
|
549
567
|
signal: req.signal,
|
|
550
568
|
});
|
|
569
|
+
if (rscResult.type === "stream") {
|
|
570
|
+
activeRscRequestId = rscResult.requestId ?? rscResult.trace?.navId;
|
|
571
|
+
activeRscTrace = rscResult.trace;
|
|
572
|
+
}
|
|
551
573
|
if (rscResult.type === "redirect")
|
|
552
574
|
return Response.redirect(new URL(rscResult.location, url.origin), rscResult.status);
|
|
553
575
|
if (rscResult.type === "not-found") return this.#renderSystemNotFoundFallbackResponse(req, url);
|
|
@@ -574,6 +596,22 @@ export class WebRouter {
|
|
|
574
596
|
onCancel: (reason: unknown) => {
|
|
575
597
|
rscResult.cancel(reason);
|
|
576
598
|
},
|
|
599
|
+
onError: ({ scope, error }) => {
|
|
600
|
+
const meta = WebRouter.#createSsrErrorLogMeta(error, {
|
|
601
|
+
scope,
|
|
602
|
+
requestId: rscResult.requestId ?? rscResult.trace?.navId,
|
|
603
|
+
pathname: url.pathname,
|
|
604
|
+
routeId: rscResult.trace?.routeId,
|
|
605
|
+
url: url.href,
|
|
606
|
+
});
|
|
607
|
+
this.#logger.errorObject(
|
|
608
|
+
`[SSR] ${scope} failed requestId=${meta.requestId ?? "(none)"} pathname=${
|
|
609
|
+
meta.pathname ?? "(unknown)"
|
|
610
|
+
} route=${meta.routeId ?? "(unknown)"} errorId=${meta.errorId}`,
|
|
611
|
+
error,
|
|
612
|
+
meta,
|
|
613
|
+
);
|
|
614
|
+
},
|
|
577
615
|
});
|
|
578
616
|
const responseStatus = rscResult.status ?? 200;
|
|
579
617
|
const responseHeaders = WebRouter.#htmlResponseHeaders(responseStatus);
|
|
@@ -642,7 +680,11 @@ export class WebRouter {
|
|
|
642
680
|
headers,
|
|
643
681
|
});
|
|
644
682
|
} catch (err) {
|
|
645
|
-
return this.#renderErrorResponse(req,
|
|
683
|
+
return this.#renderErrorResponse(req, "html", err, {
|
|
684
|
+
requestId: activeRscRequestId,
|
|
685
|
+
routeId: activeRscTrace?.routeId,
|
|
686
|
+
pathname: activeRscTrace?.pathname ?? url.pathname,
|
|
687
|
+
});
|
|
646
688
|
}
|
|
647
689
|
},
|
|
648
690
|
};
|
|
@@ -797,34 +839,110 @@ export class WebRouter {
|
|
|
797
839
|
});
|
|
798
840
|
}
|
|
799
841
|
|
|
800
|
-
#renderErrorResponse(
|
|
842
|
+
#renderErrorResponse(
|
|
843
|
+
req: Request,
|
|
844
|
+
scope: string,
|
|
845
|
+
err: unknown,
|
|
846
|
+
input: { requestId?: string; pathname?: string; routeId?: string } = {},
|
|
847
|
+
): Promise<Response> {
|
|
801
848
|
if (WebRouter.#isExpectedRequestAbort(err)) return Promise.resolve(WebRouter.#clientClosedResponse());
|
|
802
849
|
const message = err instanceof Error ? err.message : String(err);
|
|
803
|
-
|
|
850
|
+
const url = new URL(req.url);
|
|
851
|
+
const meta = WebRouter.#createSsrErrorLogMeta(err, {
|
|
852
|
+
scope,
|
|
853
|
+
requestId: input.requestId,
|
|
854
|
+
pathname: input.pathname ?? url.pathname,
|
|
855
|
+
routeId: input.routeId,
|
|
856
|
+
url: url.href,
|
|
857
|
+
});
|
|
858
|
+
this.#logger.errorObject(
|
|
859
|
+
`[SSR] render failed requestId=${meta.requestId ?? "(none)"} scope=${meta.scope} pathname=${
|
|
860
|
+
meta.pathname ?? "(unknown)"
|
|
861
|
+
} route=${meta.routeId ?? "(unknown)"} errorId=${meta.errorId}`,
|
|
862
|
+
err,
|
|
863
|
+
meta,
|
|
864
|
+
);
|
|
804
865
|
this.#hub?.broadcast({ type: "error", message });
|
|
805
866
|
return createSystemPageResponse({
|
|
806
867
|
kind: "error",
|
|
807
868
|
method: req.method,
|
|
808
|
-
pathname:
|
|
809
|
-
lang: WebRouter.#getLocale(
|
|
810
|
-
homeHref: this.#getSystemPageHomeHref(req,
|
|
811
|
-
stylesheetHref: this.#getStylesheetHref(req,
|
|
869
|
+
pathname: url.pathname,
|
|
870
|
+
lang: WebRouter.#getLocale(url.pathname, this.#artifact.i18n),
|
|
871
|
+
homeHref: this.#getSystemPageHomeHref(req, url.pathname),
|
|
872
|
+
stylesheetHref: this.#getStylesheetHref(req, url.pathname),
|
|
812
873
|
showDetails: !this.#prodMode,
|
|
813
874
|
error: err,
|
|
875
|
+
}).then((response) => {
|
|
876
|
+
response.headers.set("X-Akan-Error-Id", meta.errorId);
|
|
877
|
+
if (meta.requestId) response.headers.set("X-Akan-Rsc-Nav-Id", meta.requestId);
|
|
878
|
+
return response;
|
|
814
879
|
});
|
|
815
880
|
}
|
|
816
881
|
|
|
817
|
-
#renderRscErrorResponse(
|
|
882
|
+
#renderRscErrorResponse(
|
|
883
|
+
scope: string,
|
|
884
|
+
err: unknown,
|
|
885
|
+
req?: Request,
|
|
886
|
+
trace?: RscTraceMetadata,
|
|
887
|
+
requestId?: string,
|
|
888
|
+
): Response {
|
|
818
889
|
if (WebRouter.#isExpectedRequestAbort(err)) return WebRouter.#clientClosedResponse();
|
|
819
890
|
const message = err instanceof Error ? err.message : String(err);
|
|
820
|
-
|
|
891
|
+
const url = req ? new URL(req.url) : undefined;
|
|
892
|
+
const meta = WebRouter.#createSsrErrorLogMeta(err, {
|
|
893
|
+
scope,
|
|
894
|
+
pathname: trace?.pathname ?? url?.pathname,
|
|
895
|
+
routeId: trace?.routeId,
|
|
896
|
+
requestId: trace?.navId ?? requestId,
|
|
897
|
+
url: url?.href,
|
|
898
|
+
});
|
|
899
|
+
this.#logger.errorObject(
|
|
900
|
+
`[SSR] render failed requestId=${meta.requestId ?? "(none)"} scope=${meta.scope} pathname=${
|
|
901
|
+
meta.pathname ?? "(unknown)"
|
|
902
|
+
} route=${meta.routeId ?? "(unknown)"} errorId=${meta.errorId}`,
|
|
903
|
+
err,
|
|
904
|
+
meta,
|
|
905
|
+
);
|
|
821
906
|
this.#hub?.broadcast({ type: "error", message });
|
|
822
907
|
return new Response("Internal Server Error", {
|
|
823
908
|
status: 500,
|
|
824
|
-
headers: {
|
|
909
|
+
headers: {
|
|
910
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
911
|
+
"Cache-Control": "no-store",
|
|
912
|
+
"X-Akan-Error-Id": meta.errorId,
|
|
913
|
+
...(meta.requestId ? { "X-Akan-Rsc-Nav-Id": meta.requestId } : {}),
|
|
914
|
+
},
|
|
825
915
|
});
|
|
826
916
|
}
|
|
827
917
|
|
|
918
|
+
static #createSsrErrorLogMeta(
|
|
919
|
+
error: unknown,
|
|
920
|
+
input: { scope: string; requestId?: string; pathname?: string; routeId?: string; url?: string },
|
|
921
|
+
): SsrErrorLogMeta {
|
|
922
|
+
const requestId = WebRouter.#stringErrorProp(error, "requestId") ?? input.requestId;
|
|
923
|
+
const scope = WebRouter.#stringErrorProp(error, "scope") ?? input.scope;
|
|
924
|
+
const pathname = WebRouter.#stringErrorProp(error, "pathname") ?? input.pathname;
|
|
925
|
+
const routeId = WebRouter.#stringErrorProp(error, "routeId") ?? input.routeId;
|
|
926
|
+
const errorId =
|
|
927
|
+
WebRouter.#stringErrorProp(error, "errorId") ??
|
|
928
|
+
WebRouter.#stringErrorProp(error, "digest") ??
|
|
929
|
+
Logger.createErrorId({ requestId, scope });
|
|
930
|
+
return {
|
|
931
|
+
errorId,
|
|
932
|
+
requestId,
|
|
933
|
+
scope,
|
|
934
|
+
pathname,
|
|
935
|
+
routeId,
|
|
936
|
+
url: input.url,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
static #stringErrorProp(error: unknown, key: string): string | undefined {
|
|
941
|
+
if (typeof error !== "object" || error === null || !(key in error)) return undefined;
|
|
942
|
+
const value = (error as Record<string, unknown>)[key];
|
|
943
|
+
return typeof value === "string" && value ? value : undefined;
|
|
944
|
+
}
|
|
945
|
+
|
|
828
946
|
static #clientClosedResponse(): Response {
|
|
829
947
|
return new Response(null, {
|
|
830
948
|
status: CLIENT_CLOSED_REQUEST_STATUS,
|
package/types/common/Logger.d.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
declare const logLevels: readonly ["trace", "verbose", "debug", "log", "info", "warn", "error"];
|
|
2
2
|
export type LogLevel = (typeof logLevels)[number];
|
|
3
|
+
export type LoggerMeta = Record<string, unknown>;
|
|
4
|
+
export interface LoggerSerializedError {
|
|
5
|
+
name?: string;
|
|
6
|
+
message: string;
|
|
7
|
+
stack?: string;
|
|
8
|
+
cause?: LoggerSerializedError | string;
|
|
9
|
+
digest?: string;
|
|
10
|
+
code?: string | number;
|
|
11
|
+
status?: number;
|
|
12
|
+
}
|
|
3
13
|
export interface LoggerSinkEntry {
|
|
4
14
|
stream: "stdout" | "stderr";
|
|
5
15
|
level?: LogLevel;
|
|
6
16
|
message: string;
|
|
7
17
|
plainMessage: string;
|
|
18
|
+
meta?: LoggerMeta;
|
|
8
19
|
}
|
|
9
20
|
export type LoggerSink = (entry: LoggerSinkEntry) => void | Promise<void>;
|
|
10
21
|
/** Log-level aware logger used by Akan runtime, CLI, and application services. */
|
|
@@ -26,6 +37,7 @@ export declare class Logger {
|
|
|
26
37
|
info(msg: string, context?: string, name?: string): void;
|
|
27
38
|
warn(msg: string, context?: string, name?: string): void;
|
|
28
39
|
error(msg: string, context?: string, name?: string): void;
|
|
40
|
+
errorObject(msg: string, error: unknown, meta?: LoggerMeta, context?: string, name?: string): void;
|
|
29
41
|
raw(msg: string, method?: "console" | "process"): void;
|
|
30
42
|
rawLog(msg: string, method?: "console" | "process"): void;
|
|
31
43
|
static trace(msg: string, context?: string, name?: string): void;
|
|
@@ -35,6 +47,14 @@ export declare class Logger {
|
|
|
35
47
|
static info(msg: string, context?: string, name?: string): void;
|
|
36
48
|
static warn(msg: string, context?: string, name?: string): void;
|
|
37
49
|
static error(msg: string, context?: string, name?: string): void;
|
|
50
|
+
static errorObject(msg: string, error: unknown, meta?: LoggerMeta, context?: string, name?: string): void;
|
|
51
|
+
static createErrorId(input?: {
|
|
52
|
+
requestId?: string;
|
|
53
|
+
scope?: string;
|
|
54
|
+
sequence?: string | number;
|
|
55
|
+
}): string;
|
|
56
|
+
static serializeError(error: unknown): LoggerSerializedError;
|
|
57
|
+
static formatError(error: unknown): string;
|
|
38
58
|
static rawLog(msg?: string, method?: "console" | "process", outputStream?: "log" | "error"): void;
|
|
39
59
|
static raw(msg?: string, method?: "console" | "process", outputStream?: "log" | "error"): void;
|
|
40
60
|
}
|
package/types/common/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { isPhoneNumber } from "./isPhoneNumber.d.ts";
|
|
|
13
13
|
export { isQueryEqual } from "./isQueryEqual.d.ts";
|
|
14
14
|
export { isValidDate } from "./isValidDate.d.ts";
|
|
15
15
|
export { decodeJwtPayload } from "./jwtDecode.d.ts";
|
|
16
|
-
export { Logger, type LoggerSink, type LoggerSinkEntry, type LogLevel } from "./Logger.d.ts";
|
|
16
|
+
export { Logger, type LoggerMeta, type LoggerSerializedError, type LoggerSink, type LoggerSinkEntry, type LogLevel, } from "./Logger.d.ts";
|
|
17
17
|
export { type AkanI18nConfig, type AkanI18nConfigInput, DEFAULT_AKAN_I18N, parseAkanI18nEnv, resolveAkanI18nConfig, } from "./localeConfig.d.ts";
|
|
18
18
|
export { lowerlize } from "./lowerlize.d.ts";
|
|
19
19
|
export { mergeVersion } from "./mergeVersion.d.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AkanI18nConfig } from "akanjs/common";
|
|
1
|
+
import { type AkanI18nConfig, type LoggerMeta } from "akanjs/common";
|
|
2
2
|
import type { AkanTheme } from "akanjs/fetch";
|
|
3
3
|
import type { AkanMetricsReport } from "akanjs/service";
|
|
4
4
|
import type { ClientManifest } from "./artifact.d.ts";
|
|
@@ -8,7 +8,7 @@ import type { BaseBuildArtifact, CssAsset } from "./types.d.ts";
|
|
|
8
8
|
export interface RscPending {
|
|
9
9
|
onChunk: (data: Uint8Array) => void;
|
|
10
10
|
onEnd: () => void;
|
|
11
|
-
onError: (
|
|
11
|
+
onError: (error: string | RscWorkerErrorPayload) => void;
|
|
12
12
|
onMeta?: (meta: {
|
|
13
13
|
theme?: AkanTheme;
|
|
14
14
|
status?: number;
|
|
@@ -21,6 +21,18 @@ export interface RscPending {
|
|
|
21
21
|
}
|
|
22
22
|
export type RscRedirectMethod = "replace" | "push";
|
|
23
23
|
export type RscRedirectStatus = 303 | 307 | 308;
|
|
24
|
+
export interface RscWorkerErrorPayload {
|
|
25
|
+
message: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
stack?: string;
|
|
28
|
+
errorId?: string;
|
|
29
|
+
digest?: string;
|
|
30
|
+
requestId?: string;
|
|
31
|
+
scope?: string;
|
|
32
|
+
pathname?: string;
|
|
33
|
+
routeId?: string;
|
|
34
|
+
meta?: LoggerMeta;
|
|
35
|
+
}
|
|
24
36
|
export interface RscWorkerInvalidateCacheMessage {
|
|
25
37
|
type: "invalidate-cache";
|
|
26
38
|
reason?: string;
|
|
@@ -29,6 +41,7 @@ export interface RscWorkerInvalidateCacheMessage {
|
|
|
29
41
|
}
|
|
30
42
|
export type RscRenderResult = {
|
|
31
43
|
type: "stream";
|
|
44
|
+
requestId?: string;
|
|
32
45
|
stream: ReadableStream<Uint8Array>;
|
|
33
46
|
theme?: AkanTheme;
|
|
34
47
|
status?: number;
|
|
@@ -47,9 +60,11 @@ export type RscRenderResult = {
|
|
|
47
60
|
export declare function getRscHostMaxPendingChunks(value?: string | undefined): number;
|
|
48
61
|
export declare function nextRscHostPendingChunkCount(currentPendingChunks: number, desiredSize: number | null): number;
|
|
49
62
|
export declare function isRscHostPendingChunkOverflow(pendingChunks: number, maxPendingChunks: number): boolean;
|
|
63
|
+
export declare function createRscWorkerError(input: string | RscWorkerErrorPayload): Error;
|
|
50
64
|
export declare function createIdempotentRscRenderCancel(onCancel: (reason?: unknown) => void): (reason?: unknown) => void;
|
|
51
65
|
export declare function createRscWorkerInvalidateCacheMessage(invalidation?: string | RouteCacheInvalidation): RscWorkerInvalidateCacheMessage;
|
|
52
66
|
export declare function createRscHostRenderStream(input: {
|
|
67
|
+
requestId?: string;
|
|
53
68
|
setPending: (pending: RscPending) => void;
|
|
54
69
|
deletePending: () => void;
|
|
55
70
|
sendRenderOrQueue: () => void;
|
|
@@ -70,4 +70,8 @@ export interface SsrFromRscInput {
|
|
|
70
70
|
injectThemeInitScript?: boolean;
|
|
71
71
|
lateControl?: Promise<SsrLateRedirect | null>;
|
|
72
72
|
onCancel?: (reason?: unknown) => void;
|
|
73
|
+
onError?: (event: {
|
|
74
|
+
scope: "flight-decode" | "html-render" | "html-all-ready";
|
|
75
|
+
error: unknown;
|
|
76
|
+
}) => void;
|
|
73
77
|
}
|