feat: add form and page platform

This commit is contained in:
donghyeon-ka
2026-07-26 15:22:52 +09:00
parent fdcf0de5bf
commit b327d7370b
54 changed files with 2036 additions and 122 deletions
+58
View File
@@ -195,6 +195,7 @@ export const ERROR_REGISTRY = Object.freeze({
* requestId?: string,
* traceId?: string,
* retryAfterMs?: number,
* validationIssues?: readonly Readonly<{path: string, code: string}>[],
* userMessageKey: string,
* action: ErrorAction,
* causeClass?: string
@@ -211,6 +212,7 @@ export const ERROR_REGISTRY = Object.freeze({
* requestId?: string,
* traceId?: string,
* retryAfterMs?: number,
* validationIssues?: readonly Readonly<{path: string, code: string}>[],
* causeClass?: string
* }} [details]
* @returns {ApiFailure}
@@ -236,6 +238,24 @@ export function createFailure(kind, operationId, attempt, details = {}) {
...(typeof details.retryAfterMs === "number"
? { retryAfterMs: details.retryAfterMs }
: {}),
...(Array.isArray(details.validationIssues)
? {
validationIssues: Object.freeze(
details.validationIssues
.filter(
(issue) =>
issue &&
typeof issue === "object" &&
typeof issue.path === "string" &&
typeof issue.code === "string",
)
.slice(0, 50)
.map((issue) =>
Object.freeze({ path: issue.path, code: issue.code }),
),
),
}
: {}),
...(typeof details.causeClass === "string"
? { causeClass: details.causeClass }
: {}),
@@ -244,6 +264,44 @@ export function createFailure(kind, operationId, attempt, details = {}) {
});
}
/**
* Projects an untrusted 422 details payload into the only validation metadata
* allowed to cross the HTTP boundary. Backend copy and additional values are
* deliberately discarded.
*
* @param {unknown} value
* @returns {readonly Readonly<{path: string, code: string}>[]}
*/
export function safeValidationIssues(value) {
if (!value || typeof value !== "object") return Object.freeze([]);
const candidate =
/** @type {{issues?: unknown, fieldErrors?: unknown}} */ (value);
const issues = Array.isArray(candidate.issues)
? candidate.issues
: Array.isArray(candidate.fieldErrors)
? candidate.fieldErrors
: [];
return Object.freeze(
issues
.filter(
(issue) =>
issue &&
typeof issue === "object" &&
typeof issue.path === "string" &&
typeof issue.code === "string" &&
issue.path.length <= 120 &&
issue.code.length <= 80,
)
.slice(0, 50)
.map((issue) =>
Object.freeze({
path: issue.path,
code: issue.code,
}),
),
);
}
/** @param {number} status */
export function kindForStatus(status) {
if (status === 401) return "AUTH_REQUIRED";