@formadapter/orpc 0.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ludicrous
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # `@formadapter/orpc`
2
+
3
+ Connect a FormAdapter form to an oRPC procedure without coupling the form to a
4
+ router, transport, or UI adapter.
5
+
6
+ ```sh
7
+ bun add @formadapter/orpc @orpc/client
8
+ ```
9
+
10
+ ## Client procedures
11
+
12
+ `createORPCSubmission` returns a FormAdapter `onSubmit` handler. It sends the
13
+ schema input from `SubmitContext` by default, not the transformed output passed
14
+ as the first argument. That lets the server run the schema's transforms again
15
+ at its trust boundary. The form's abort signal is forwarded through oRPC caller
16
+ options.
17
+
18
+ ```tsx
19
+ import { createORPCSubmission } from "@formadapter/orpc";
20
+
21
+ <Account.Form onSubmit={createORPCSubmission(orpc.account.update)} />;
22
+ ```
23
+
24
+ The procedure output is wrapped as `{ status: "success", data }`. A procedure
25
+ that already returns a well-formed FormAdapter `SubmissionState` passes through
26
+ unchanged.
27
+
28
+ Use `mapInput` only when the form and procedure intentionally have different
29
+ wire shapes. Caller options and oRPC context can be static values or per-submit
30
+ factories:
31
+
32
+ ```ts
33
+ const submit = createORPCSubmission(orpc.account.update, {
34
+ mapInput: (values) => ({ account: values }),
35
+ context: (_values, context) => ({ requestId: context.formData.get("requestId") }),
36
+ options: () => ({ lastEventId: currentEventId }),
37
+ });
38
+ ```
39
+
40
+ Unknown thrown values and server-side failures receive a safe generic message.
41
+ Use `mapError` for application-specific errors; returning `undefined` falls
42
+ through to the built-in mappings.
43
+
44
+ ## Typed form errors
45
+
46
+ The package has no `@orpc/server` dependency. It exports a structural Standard
47
+ Schema error declaration that can be installed once on an oRPC base builder:
48
+
49
+ ```ts
50
+ import { FORM_SUBMISSION_ERROR_MAP } from "@formadapter/orpc";
51
+ import { os } from "@orpc/server";
52
+
53
+ const formProcedure = os.errors(FORM_SUBMISSION_ERROR_MAP);
54
+ ```
55
+
56
+ In a handler, return the error through oRPC's generated constructor:
57
+
58
+ ```ts
59
+ throw errors.FORM_SUBMISSION_FAILED({
60
+ data: submissionFailure({
61
+ fieldErrors: { email: ["That address is already registered."] },
62
+ }),
63
+ });
64
+ ```
65
+
66
+ `createORPCSubmission` maps that data directly into form state. It also maps
67
+ oRPC's built-in `BAD_REQUEST` `{ issues }` payload, preserving nested and array
68
+ Standard Schema paths.
69
+
70
+ That `BAD_REQUEST` mapping is runtime fallback behavior, not a declared custom
71
+ error. It works with oRPC's default input-validation payload. If an interceptor
72
+ replaces or removes `data.issues`, use a typed custom error or `mapError` instead.
73
+
74
+ ## Actionable procedures
75
+
76
+ Use `createORPCActionSubmission` for the tuple returned by `.actionable()`.
77
+ This is separate because actionable procedures return JSON errors instead of
78
+ throwing ordinary `ORPCError` instances and cannot accept an AbortSignal.
79
+ Unexpected throws are deliberately re-thrown so framework redirects and
80
+ not-found control flow continue to work.
81
+
82
+ `@orpc/react`'s `createFormAction` is a different integration: it consumes
83
+ bracket-notation `FormData` and returns no result state. FormAdapter uses its
84
+ typed object submission path here so errors can be applied to the form.
@@ -0,0 +1,127 @@
1
+ import { FailedSubmission, MaybePromise, StandardResult, SubmissionState, SuccessfulSubmission } from "@formadapter/core";
2
+
3
+ //#region src/types.d.ts
4
+ /** The structural callable shape shared by oRPC procedure clients. */
5
+ type ORPCCallable = (...args: never[]) => unknown;
6
+ type ClientArguments<Client extends ORPCCallable> = Client extends ((...args: infer Arguments) => unknown) ? Arguments : never;
7
+ type SecondArgument<Arguments extends readonly unknown[]> = Arguments extends readonly [unknown, ...infer Rest] ? Rest[0] : never;
8
+ type ClientResult<Client extends ORPCCallable> = Client extends ((...args: never[]) => infer Result) ? Result : never;
9
+ type NonNever<Value, Fallback> = [Value] extends [never] ? Fallback : Value;
10
+ /** The input accepted by an oRPC procedure client. */
11
+ type ORPCClientInput<Client extends ORPCCallable> = ClientArguments<Client>[0];
12
+ /** The resolved output returned by an oRPC procedure client. */
13
+ type ORPCClientOutput<Client extends ORPCCallable> = Awaited<ClientResult<Client>>;
14
+ /** The error recorded on oRPC's structurally typed PromiseWithError. */
15
+ type ORPCClientError<Client extends ORPCCallable> = ClientResult<Client> extends {
16
+ readonly __error?: {
17
+ readonly type: infer ErrorType;
18
+ };
19
+ } ? ErrorType : unknown;
20
+ type InferredCallerOptions<Client extends ORPCCallable> = NonNullable<SecondArgument<ClientArguments<Client>>>;
21
+ /** Caller options accepted by the client, excluding values controlled by this adapter. */
22
+ type ORPCCallerOptions<Client extends ORPCCallable> = NonNever<InferredCallerOptions<Client> extends object ? Omit<InferredCallerOptions<Client>, "context" | "signal"> : never, Readonly<Record<never, never>>>;
23
+ /** The per-call oRPC client context, if the client defines one. */
24
+ type ORPCClientContext<Client extends ORPCCallable> = NonNever<InferredCallerOptions<Client> extends {
25
+ readonly context?: infer Context;
26
+ } ? Context : never, Readonly<Record<never, never>>>;
27
+ type ClientRequiresContext<Client extends ORPCCallable> = [InferredCallerOptions<Client>] extends [never] ? false : InferredCallerOptions<Client> extends {
28
+ readonly context: unknown;
29
+ } ? true : false;
30
+ /** The structural FormAdapter context supplied to an oRPC submission handler. */
31
+ interface ORPCSubmitContext<Input> {
32
+ /** Prepared schema input before transforms. */
33
+ readonly input: Input;
34
+ readonly formData: FormData;
35
+ readonly signal: AbortSignal;
36
+ }
37
+ type DataFromSubmissionState<State> = State extends SuccessfulSubmission<infer Data> ? Data : never;
38
+ /** Unwraps procedures that already return a shared SubmissionState. */
39
+ type ORPCSubmissionData<Output> = [Output] extends [SubmissionState] ? DataFromSubmissionState<Output> : Output;
40
+ type ORPCSubmissionHandler<Input, Values, Data> = (values: Values, context: ORPCSubmitContext<Input>) => Promise<SubmissionState<Data>>;
41
+ type ORPCValueOrFactory<Value, Values, Input> = Value | ((values: Values, context: ORPCSubmitContext<Input>) => MaybePromise<Value>);
42
+ interface ORPCErrorLike<Data = unknown> {
43
+ readonly code: string;
44
+ readonly data?: Data;
45
+ readonly defined?: boolean;
46
+ readonly message: string;
47
+ readonly status: number;
48
+ }
49
+ type ORPCErrorMapper<ErrorType, Values, Input, Data> = (error: ErrorType, values: Values, context: ORPCSubmitContext<Input>) => MaybePromise<SubmissionState<Data> | undefined>;
50
+ interface ORPCSubmissionOptionsBase<Client extends ORPCCallable, Input, Values> {
51
+ /** Maps form values/context to the procedure input. Defaults to context.input. */
52
+ readonly mapInput?: (values: Values, context: ORPCSubmitContext<Input>) => MaybePromise<ORPCClientInput<Client>>;
53
+ /** Handles application-specific oRPC errors before the built-in mappings run. */
54
+ readonly mapError?: ORPCErrorMapper<ORPCClientError<Client>, Values, Input, ORPCSubmissionData<ORPCClientOutput<Client>>>;
55
+ /** Static caller options or a per-submit factory. Signal and context are managed separately. */
56
+ readonly options?: ORPCValueOrFactory<ORPCCallerOptions<Client>, Values, Input>;
57
+ /** Safe message used for unknown, malformed, and server-side failures. */
58
+ readonly unknownErrorMessage?: string;
59
+ }
60
+ type ORPCContextOption<Client extends ORPCCallable, Input, Values> = ClientRequiresContext<Client> extends true ? {
61
+ readonly context: ORPCValueOrFactory<ORPCClientContext<Client>, Values, Input>;
62
+ } : {
63
+ readonly context?: ORPCValueOrFactory<ORPCClientContext<Client>, Values, Input>;
64
+ };
65
+ type ORPCSubmissionOptions<Client extends ORPCCallable, Input = ORPCClientInput<Client>, Values = unknown> = ORPCSubmissionOptionsBase<Client, Input, Values> & ORPCContextOption<Client, Input, Values>;
66
+ type ORPCSubmissionOptionsRest<Client extends ORPCCallable, Input, Values> = ClientRequiresContext<Client> extends true ? [options: ORPCSubmissionOptions<Client, Input, Values>] : [options?: ORPCSubmissionOptions<Client, Input, Values>];
67
+ /** JSON-safe error returned by an oRPC actionable procedure. */
68
+ interface ORPCActionError<Data = unknown> extends ORPCErrorLike<Data> {
69
+ readonly defined: boolean;
70
+ }
71
+ type ORPCActionResult<Output, ErrorType extends ORPCErrorLike> = readonly [error: null, data: Output] | readonly [error: ErrorType, data: undefined];
72
+ type ORPCAction<Input, Output, ErrorType extends ORPCErrorLike> = (input: Input) => PromiseLike<ORPCActionResult<Output, ErrorType>>;
73
+ interface ORPCActionSubmissionOptions<ActionInput, Output, ErrorType extends ORPCErrorLike, Input = ActionInput, Values = unknown> {
74
+ readonly mapInput?: (values: Values, context: ORPCSubmitContext<Input>) => MaybePromise<ActionInput>;
75
+ readonly mapError?: ORPCErrorMapper<ErrorType, Values, Input, ORPCSubmissionData<Output>>;
76
+ readonly unknownErrorMessage?: string;
77
+ }
78
+ /** Minimal Standard Schema shape used by oRPC error data declarations. */
79
+ interface ORPCStandardSchema<Input, Output = Input> {
80
+ readonly "~standard": {
81
+ readonly version: 1;
82
+ readonly vendor: string;
83
+ readonly types?: {
84
+ readonly input: Input;
85
+ readonly output: Output;
86
+ };
87
+ readonly validate: (value: unknown) => StandardResult<Output>;
88
+ };
89
+ }
90
+ /**
91
+ * Kept as an object type alias (rather than an interface) so it is assignable
92
+ * to oRPC 1.x's open-ended `ErrorMap` without widening the known error code.
93
+ */
94
+ type FormSubmissionErrorMap = {
95
+ readonly FORM_SUBMISSION_FAILED: {
96
+ readonly data: ORPCStandardSchema<FailedSubmission>;
97
+ readonly message: "Form submission failed";
98
+ readonly status: 422;
99
+ };
100
+ };
101
+ //#endregion
102
+ //#region src/error-data.d.ts
103
+ declare const FORM_SUBMISSION_FAILED_CODE: "FORM_SUBMISSION_FAILED";
104
+ /** Runtime guard for error data sent through FORM_SUBMISSION_FAILED. */
105
+ declare function isFormSubmissionFailureData(value: unknown): value is FailedSubmission;
106
+ /** Standard Schema used for a typed oRPC FORM_SUBMISSION_FAILED error. */
107
+ declare const formSubmissionFailureSchema: ORPCStandardSchema<FailedSubmission>;
108
+ /** Reusable argument for `os.errors(FORM_SUBMISSION_ERROR_MAP)`. */
109
+ declare const FORM_SUBMISSION_ERROR_MAP: FormSubmissionErrorMap;
110
+ //#endregion
111
+ //#region src/errors.d.ts
112
+ declare const DEFAULT_ORPC_ERROR_MESSAGE = "Unable to submit the form. Please try again.";
113
+ declare function isORPCErrorLike(value: unknown): value is ORPCErrorLike;
114
+ declare function orpcErrorToSubmission(error: unknown, unknownErrorMessage?: string): SubmissionState | undefined;
115
+ declare function unknownErrorToSubmission(error: unknown, unknownErrorMessage?: string): SubmissionState;
116
+ //#endregion
117
+ //#region src/submission.d.ts
118
+ /** Adapts a normal oRPC procedure client to a FormAdapter onSubmit handler. */
119
+ declare function createORPCSubmission<Client extends ORPCCallable, Input = ORPCClientInput<Client>, Values = unknown>(client: Client, ...[options]: ORPCSubmissionOptionsRest<Client, Input, Values>): ORPCSubmissionHandler<Input, Values, ORPCSubmissionData<ORPCClientOutput<Client>>>;
120
+ /**
121
+ * Adapts an oRPC `.actionable()` tuple to FormAdapter.
122
+ * Unexpected throws are intentionally preserved for framework redirects/not-found control flow.
123
+ */
124
+ declare function createORPCActionSubmission<ActionInput, Output, ErrorType extends ORPCErrorLike, Input = ActionInput, Values = unknown>(action: ORPCAction<ActionInput, Output, ErrorType>, options?: ORPCActionSubmissionOptions<ActionInput, Output, ErrorType, Input, Values>): ORPCSubmissionHandler<Input, Values, ORPCSubmissionData<Output>>;
125
+ //#endregion
126
+ export { DEFAULT_ORPC_ERROR_MESSAGE, FORM_SUBMISSION_ERROR_MAP, FORM_SUBMISSION_FAILED_CODE, type FormSubmissionErrorMap, type ORPCAction, type ORPCActionError, type ORPCActionResult, type ORPCActionSubmissionOptions, type ORPCCallable, type ORPCCallerOptions, type ORPCClientContext, type ORPCClientError, type ORPCClientInput, type ORPCClientOutput, type ORPCErrorLike, type ORPCErrorMapper, type ORPCStandardSchema, type ORPCSubmissionData, type ORPCSubmissionHandler, type ORPCSubmissionOptions, type ORPCSubmitContext, type ORPCValueOrFactory, createORPCActionSubmission, createORPCSubmission, formSubmissionFailureSchema, isFormSubmissionFailureData, isORPCErrorLike, orpcErrorToSubmission, unknownErrorToSubmission };
127
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,139 @@
1
+ import { isSubmissionState, issuePath, issuesToFieldErrors, submissionFailure, submissionSuccess } from "@formadapter/core";
2
+ //#region src/error-data.ts
3
+ const FORM_SUBMISSION_FAILED_CODE = "FORM_SUBMISSION_FAILED";
4
+ /** Runtime guard for error data sent through FORM_SUBMISSION_FAILED. */
5
+ function isFormSubmissionFailureData(value) {
6
+ try {
7
+ return isSubmissionState(value) && value.status === "error";
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+ function validateFailureData(value) {
13
+ return isFormSubmissionFailureData(value) ? { value } : { issues: [{ message: "Expected a FormAdapter failed submission." }] };
14
+ }
15
+ /** Standard Schema used for a typed oRPC FORM_SUBMISSION_FAILED error. */
16
+ const formSubmissionFailureSchema = { "~standard": {
17
+ validate: validateFailureData,
18
+ vendor: "formadapter",
19
+ version: 1
20
+ } };
21
+ /** Reusable argument for `os.errors(FORM_SUBMISSION_ERROR_MAP)`. */
22
+ const FORM_SUBMISSION_ERROR_MAP = { FORM_SUBMISSION_FAILED: {
23
+ data: formSubmissionFailureSchema,
24
+ message: "Form submission failed",
25
+ status: 422
26
+ } };
27
+ //#endregion
28
+ //#region src/errors.ts
29
+ const DEFAULT_ORPC_ERROR_MESSAGE = "Unable to submit the form. Please try again.";
30
+ function isRecord(value) {
31
+ return typeof value === "object" && value !== null;
32
+ }
33
+ function isPathSegment(value) {
34
+ try {
35
+ if (typeof value === "string" || typeof value === "number" || typeof value === "symbol") return true;
36
+ return isRecord(value) && "key" in value && (typeof value.key === "string" || typeof value.key === "number" || typeof value.key === "symbol");
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+ function isStandardIssue(value) {
42
+ try {
43
+ if (!isRecord(value) || typeof value.message !== "string") return false;
44
+ return value.path === void 0 || Array.isArray(value.path) && value.path.every(isPathSegment);
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+ function isORPCErrorLike(value) {
50
+ try {
51
+ return isRecord(value) && typeof value.code === "string" && typeof value.message === "string" && typeof value.status === "number" && Number.isFinite(value.status);
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ function validationFailure(issues, fallbackMessage) {
57
+ const fieldIssues = issues.filter((issue) => issuePath(issue).length > 0);
58
+ const formErrors = issues.filter((issue) => issuePath(issue).length === 0).map((issue) => issue.message);
59
+ return submissionFailure({
60
+ errorKind: "validation",
61
+ fieldErrors: issuesToFieldErrors(fieldIssues),
62
+ formErrors: fieldIssues.length === 0 && formErrors.length === 0 ? [fallbackMessage] : formErrors
63
+ });
64
+ }
65
+ function issuesFromError(error) {
66
+ if (!isRecord(error.data) || !Array.isArray(error.data.issues)) return;
67
+ return error.data.issues.filter(isStandardIssue);
68
+ }
69
+ function orpcErrorToSubmission(error, unknownErrorMessage = DEFAULT_ORPC_ERROR_MESSAGE) {
70
+ try {
71
+ if (!isORPCErrorLike(error)) return void 0;
72
+ if (error.code === "FORM_SUBMISSION_FAILED" && isFormSubmissionFailureData(error.data)) return error.data;
73
+ const issues = error.code === "BAD_REQUEST" ? issuesFromError(error) : void 0;
74
+ if (issues) return validationFailure(issues, error.message || unknownErrorMessage);
75
+ if (error.status >= 400 && error.status < 500 && error.message.trim().length > 0) return submissionFailure({
76
+ errorKind: "business",
77
+ formErrors: [error.message]
78
+ });
79
+ return submissionFailure({
80
+ errorKind: "transport",
81
+ formErrors: [unknownErrorMessage]
82
+ });
83
+ } catch {
84
+ return;
85
+ }
86
+ }
87
+ function unknownErrorToSubmission(error, unknownErrorMessage = DEFAULT_ORPC_ERROR_MESSAGE) {
88
+ return orpcErrorToSubmission(error, unknownErrorMessage) ?? submissionFailure({
89
+ errorKind: "transport",
90
+ formErrors: [unknownErrorMessage]
91
+ });
92
+ }
93
+ //#endregion
94
+ //#region src/submission.ts
95
+ async function resolveValue(value, values, context) {
96
+ if (typeof value === "function") return await value(values, context);
97
+ return value;
98
+ }
99
+ function normalizeOutput(output) {
100
+ return isSubmissionState(output) ? output : submissionSuccess(output);
101
+ }
102
+ function safeCallerOptions(value) {
103
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
104
+ const result = { ...value };
105
+ delete result.context;
106
+ delete result.signal;
107
+ return result;
108
+ }
109
+ /** Adapts a normal oRPC procedure client to a FormAdapter onSubmit handler. */
110
+ function createORPCSubmission(client, ...[options]) {
111
+ return async (values, context) => {
112
+ const input = options?.mapInput ? await options.mapInput(values, context) : context.input;
113
+ const suppliedOptions = await resolveValue(options?.options, values, context);
114
+ const suppliedContext = await resolveValue(options?.context, values, context);
115
+ const callerOptions = safeCallerOptions(suppliedOptions);
116
+ if (suppliedContext !== void 0) callerOptions.context = suppliedContext;
117
+ callerOptions.signal = context.signal;
118
+ try {
119
+ return normalizeOutput(await client(input, callerOptions));
120
+ } catch (error) {
121
+ return (options?.mapError ? await options.mapError(error, values, context) : void 0) ?? unknownErrorToSubmission(error, options?.unknownErrorMessage);
122
+ }
123
+ };
124
+ }
125
+ /**
126
+ * Adapts an oRPC `.actionable()` tuple to FormAdapter.
127
+ * Unexpected throws are intentionally preserved for framework redirects/not-found control flow.
128
+ */
129
+ function createORPCActionSubmission(action, options = {}) {
130
+ return async (values, context) => {
131
+ const [error, output] = await action(options.mapInput ? await options.mapInput(values, context) : context.input);
132
+ if (error !== null) return (options.mapError ? await options.mapError(error, values, context) : void 0) ?? unknownErrorToSubmission(error, options.unknownErrorMessage);
133
+ return normalizeOutput(output);
134
+ };
135
+ }
136
+ //#endregion
137
+ export { DEFAULT_ORPC_ERROR_MESSAGE, FORM_SUBMISSION_ERROR_MAP, FORM_SUBMISSION_FAILED_CODE, createORPCActionSubmission, createORPCSubmission, formSubmissionFailureSchema, isFormSubmissionFailureData, isORPCErrorLike, orpcErrorToSubmission, unknownErrorToSubmission };
138
+
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["call"],"sources":["../src/error-data.ts","../src/errors.ts","../src/submission.ts"],"sourcesContent":["import { isSubmissionState } from \"@formadapter/core\";\nimport type { FailedSubmission, StandardResult } from \"@formadapter/core\";\n\nimport type {\n FormSubmissionErrorMap,\n ORPCStandardSchema,\n} from \"./types\";\n\nexport const FORM_SUBMISSION_FAILED_CODE = \"FORM_SUBMISSION_FAILED\" as const;\n\n/** Runtime guard for error data sent through FORM_SUBMISSION_FAILED. */\nexport function isFormSubmissionFailureData(\n value: unknown,\n): value is FailedSubmission {\n try {\n return isSubmissionState(value) && value.status === \"error\";\n } catch {\n return false;\n }\n}\n\nfunction validateFailureData(value: unknown): StandardResult<FailedSubmission> {\n return isFormSubmissionFailureData(value)\n ? { value }\n : {\n issues: [\n {\n message: \"Expected a FormAdapter failed submission.\",\n },\n ],\n };\n}\n\n/** Standard Schema used for a typed oRPC FORM_SUBMISSION_FAILED error. */\nexport const formSubmissionFailureSchema: ORPCStandardSchema<FailedSubmission> = {\n \"~standard\": {\n validate: validateFailureData,\n vendor: \"formadapter\",\n version: 1,\n },\n};\n\n/** Reusable argument for `os.errors(FORM_SUBMISSION_ERROR_MAP)`. */\nexport const FORM_SUBMISSION_ERROR_MAP: FormSubmissionErrorMap = {\n FORM_SUBMISSION_FAILED: {\n data: formSubmissionFailureSchema,\n message: \"Form submission failed\",\n status: 422,\n },\n};\n","import {\n issuePath,\n issuesToFieldErrors,\n submissionFailure,\n} from \"@formadapter/core\";\nimport type {\n StandardIssue,\n SubmissionState,\n} from \"@formadapter/core\";\n\nimport {\n FORM_SUBMISSION_FAILED_CODE,\n isFormSubmissionFailureData,\n} from \"./error-data\";\nimport type { ORPCErrorLike } from \"./types\";\n\nexport const DEFAULT_ORPC_ERROR_MESSAGE =\n \"Unable to submit the form. Please try again.\";\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isPathSegment(value: unknown): boolean {\n try {\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"symbol\"\n ) {\n return true;\n }\n return (\n isRecord(value) &&\n \"key\" in value &&\n (typeof value.key === \"string\" ||\n typeof value.key === \"number\" ||\n typeof value.key === \"symbol\")\n );\n } catch {\n return false;\n }\n}\n\nfunction isStandardIssue(value: unknown): value is StandardIssue {\n try {\n if (!isRecord(value) || typeof value.message !== \"string\") return false;\n return (\n value.path === undefined ||\n (Array.isArray(value.path) && value.path.every(isPathSegment))\n );\n } catch {\n return false;\n }\n}\n\nexport function isORPCErrorLike(value: unknown): value is ORPCErrorLike {\n try {\n return (\n isRecord(value) &&\n typeof value.code === \"string\" &&\n typeof value.message === \"string\" &&\n typeof value.status === \"number\" &&\n Number.isFinite(value.status)\n );\n } catch {\n return false;\n }\n}\n\nfunction validationFailure(\n issues: readonly StandardIssue[],\n fallbackMessage: string,\n): SubmissionState {\n const fieldIssues = issues.filter((issue) => issuePath(issue).length > 0);\n const formErrors = issues\n .filter((issue) => issuePath(issue).length === 0)\n .map((issue) => issue.message);\n\n return submissionFailure({\n errorKind: \"validation\",\n fieldErrors: issuesToFieldErrors(fieldIssues),\n formErrors:\n fieldIssues.length === 0 && formErrors.length === 0\n ? [fallbackMessage]\n : formErrors,\n });\n}\n\nfunction issuesFromError(error: ORPCErrorLike): readonly StandardIssue[] | undefined {\n if (!isRecord(error.data) || !Array.isArray(error.data.issues)) {\n return undefined;\n }\n return error.data.issues.filter(isStandardIssue);\n}\n\nexport function orpcErrorToSubmission(\n error: unknown,\n unknownErrorMessage: string = DEFAULT_ORPC_ERROR_MESSAGE,\n): SubmissionState | undefined {\n try {\n if (!isORPCErrorLike(error)) return undefined;\n\n if (\n error.code === FORM_SUBMISSION_FAILED_CODE &&\n isFormSubmissionFailureData(error.data)\n ) {\n return error.data;\n }\n\n const issues = error.code === \"BAD_REQUEST\"\n ? issuesFromError(error)\n : undefined;\n if (issues) {\n return validationFailure(issues, error.message || unknownErrorMessage);\n }\n\n if (\n error.status >= 400 &&\n error.status < 500 &&\n error.message.trim().length > 0\n ) {\n return submissionFailure({\n errorKind: \"business\",\n formErrors: [error.message],\n });\n }\n\n return submissionFailure({\n errorKind: \"transport\",\n formErrors: [unknownErrorMessage],\n });\n } catch {\n return undefined;\n }\n}\n\nexport function unknownErrorToSubmission(\n error: unknown,\n unknownErrorMessage: string = DEFAULT_ORPC_ERROR_MESSAGE,\n): SubmissionState {\n return (\n orpcErrorToSubmission(error, unknownErrorMessage) ??\n submissionFailure({\n errorKind: \"transport\",\n formErrors: [unknownErrorMessage],\n })\n );\n}\n","import {\n isSubmissionState,\n submissionSuccess,\n} from \"@formadapter/core\";\nimport type {\n SubmissionState,\n} from \"@formadapter/core\";\n\nimport { unknownErrorToSubmission } from \"./errors\";\nimport type {\n ORPCAction,\n ORPCActionSubmissionOptions,\n ORPCCallable,\n ORPCClientInput,\n ORPCClientOutput,\n ORPCErrorLike,\n ORPCSubmissionData,\n ORPCSubmissionHandler,\n ORPCSubmissionOptionsRest,\n ORPCSubmitContext,\n ORPCValueOrFactory,\n} from \"./types\";\n\nasync function resolveValue<Value, Values, Input>(\n value: ORPCValueOrFactory<Value, Values, Input> | undefined,\n values: Values,\n context: ORPCSubmitContext<Input>,\n): Promise<Value | undefined> {\n if (typeof value === \"function\") {\n return await (\n value as (\n currentValues: Values,\n currentContext: typeof context,\n ) => Value | PromiseLike<Value>\n )(values, context);\n }\n return value;\n}\n\nfunction normalizeOutput<Output>(\n output: Output,\n): SubmissionState<ORPCSubmissionData<Output>> {\n return isSubmissionState(output)\n ? (output as SubmissionState<ORPCSubmissionData<Output>>)\n : submissionSuccess(output as ORPCSubmissionData<Output>);\n}\n\nfunction safeCallerOptions(value: unknown): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {};\n }\n const result = { ...(value as Readonly<Record<string, unknown>>) };\n delete result.context;\n delete result.signal;\n return result;\n}\n\n/** Adapts a normal oRPC procedure client to a FormAdapter onSubmit handler. */\nexport function createORPCSubmission<\n Client extends ORPCCallable,\n Input = ORPCClientInput<Client>,\n Values = unknown,\n>(\n client: Client,\n ...[options]: ORPCSubmissionOptionsRest<Client, Input, Values>\n): ORPCSubmissionHandler<\n Input,\n Values,\n ORPCSubmissionData<ORPCClientOutput<Client>>\n> {\n return async (values, context) => {\n const input = options?.mapInput\n ? await options.mapInput(values, context)\n : context.input;\n const suppliedOptions = await resolveValue(\n options?.options,\n values,\n context,\n );\n const suppliedContext = await resolveValue(\n options?.context,\n values,\n context,\n );\n const callerOptions = safeCallerOptions(suppliedOptions);\n if (suppliedContext !== undefined) {\n callerOptions.context = suppliedContext;\n }\n callerOptions.signal = context.signal;\n\n try {\n const call = client as unknown as (\n procedureInput: ORPCClientInput<Client>,\n procedureOptions: Readonly<Record<string, unknown>>,\n ) => unknown;\n const output = (await call(\n input as ORPCClientInput<Client>,\n callerOptions,\n )) as ORPCClientOutput<Client>;\n return normalizeOutput(output);\n } catch (error) {\n const custom = options?.mapError\n ? await options.mapError(\n error as Parameters<NonNullable<typeof options.mapError>>[0],\n values,\n context,\n )\n : undefined;\n return (\n custom ??\n (unknownErrorToSubmission(\n error,\n options?.unknownErrorMessage,\n ) as SubmissionState<ORPCSubmissionData<ORPCClientOutput<Client>>>)\n );\n }\n };\n}\n\n/**\n * Adapts an oRPC `.actionable()` tuple to FormAdapter.\n * Unexpected throws are intentionally preserved for framework redirects/not-found control flow.\n */\nexport function createORPCActionSubmission<\n ActionInput,\n Output,\n ErrorType extends ORPCErrorLike,\n Input = ActionInput,\n Values = unknown,\n>(\n action: ORPCAction<ActionInput, Output, ErrorType>,\n options: ORPCActionSubmissionOptions<\n ActionInput,\n Output,\n ErrorType,\n Input,\n Values\n > = {},\n): ORPCSubmissionHandler<Input, Values, ORPCSubmissionData<Output>> {\n return async (values, context) => {\n const input = options.mapInput\n ? await options.mapInput(values, context)\n : context.input;\n const [error, output] = await action(input as ActionInput);\n\n if (error !== null) {\n const custom = options.mapError\n ? await options.mapError(error, values, context)\n : undefined;\n return (\n custom ??\n (unknownErrorToSubmission(\n error,\n options.unknownErrorMessage,\n ) as SubmissionState<ORPCSubmissionData<Output>>)\n );\n }\n\n return normalizeOutput(output as Output);\n };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@formadapter/orpc",
3
+ "version": "0.0.0",
4
+ "description": "Type-safe oRPC submissions for FormAdapter forms.",
5
+ "keywords": [
6
+ "forms",
7
+ "orpc",
8
+ "rpc",
9
+ "standard-schema",
10
+ "typescript"
11
+ ],
12
+ "homepage": "https://formadapter.com",
13
+ "bugs": {
14
+ "url": "https://github.com/ludicroushq/formadapter/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ludicroushq/formadapter.git",
19
+ "directory": "packages/orpc"
20
+ },
21
+ "license": "MIT",
22
+ "author": "ludicrous",
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "main": "./dist/index.js",
34
+ "module": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "import": "./dist/index.js",
40
+ "default": "./dist/index.js"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "dependencies": {
45
+ "@formadapter/core": "^0.0.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@orpc/client": ">=1.14.0 <2.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@formadapter/react": "^0.0.0",
52
+ "@orpc/client": "1.14.7",
53
+ "@orpc/server": "1.14.7"
54
+ },
55
+ "scripts": {
56
+ "build": "tsdown",
57
+ "clean": "rm -rf dist tsconfig.tsbuildinfo .turbo",
58
+ "dev": "tsdown --watch",
59
+ "test": "vitest run --config vitest.config.ts",
60
+ "test:coverage": "vitest run --config vitest.config.ts --coverage",
61
+ "typecheck": "tsc --project tsconfig.json --noEmit"
62
+ }
63
+ }