kubb-plugin-sveltekit-remote-functions 1.0.0-20260913.9

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 iturner100
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,160 @@
1
+ <div align="center">
2
+
3
+ [![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![License][license-src]][license-href]
4
+
5
+ <h4>
6
+ <a href="https://kubb.dev" target="_blank" rel="noopener noreferrer">Kubb</a>
7
+ <span> · </span>
8
+ <a href="https://github.com/iturner100/kubb-plugin-sveltekit-remote-functions/issues" target="_blank" rel="noopener noreferrer">Report Bug</a>
9
+ <span> · </span>
10
+ <a href="https://github.com/iturner100/kubb-plugin-sveltekit-remote-functions/issues" target="_blank" rel="noopener noreferrer">Request Feature</a>
11
+ </h4>
12
+
13
+ </div>
14
+
15
+ <br />
16
+
17
+ # kubb-plugin-sveltekit-remote-functions
18
+
19
+ ### Generate SvelteKit remote functions with Kubb
20
+
21
+ Generates [SvelteKit remote functions](https://svelte.dev/docs/kit/remote-functions) (`query`, `query.batch`, `query.live`, `command`, `form`, `prerender`) from an OpenAPI spec — one `*.remote.ts` file per operation. This plugin does not perform HTTP calls itself: it depends on [`@kubb/plugin-fetch`](https://kubb.dev/plugins/plugin-fetch) and wraps the async function that plugin already generates for each operation, swapping in SvelteKit's request-scoped `event.fetch` via a [custom transport](https://kubb.dev/plugins/plugin-fetch/guide/transport) and registering a pass-through [response interceptor](https://kubb.dev/plugins/plugin-fetch/guide/ interceptors) as an extension point for future SvelteKit-specific response handling.
22
+
23
+ Every generated remote function uses the operation's [`@kubb/plugin-zod`](https://kubb.dev/plugins/plugin-zod) combined "options" schema directly as its argument schema, so this plugin has two hard configuration requirements (see [Requirements](#requirements)).
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ pnpm add kubb-plugin-sveltekit-remote-functions @kubb/plugin-fetch @kubb/plugin-zod
29
+ # or
30
+ npm install kubb-plugin-sveltekit-remote-functions @kubb/plugin-fetch @kubb/plugin-zod
31
+ # or
32
+ bun add kubb-plugin-sveltekit-remote-functions @kubb/plugin-fetch @kubb/plugin-zod
33
+ ```
34
+
35
+ ## Requirements
36
+
37
+ - `pluginFetch()` and `pluginZod()` must both be present in the same pipeline — hard, driver-enforced dependencies. This plugin throws a descriptive error at startup if either is missing.
38
+ - `pluginZod({ inferred: true })` — `@kubb/plugin-zod` only generates the combined `{ body, path, query, headers }` "options" schema this plugin uses directly (as well as the per-parameter grouped schemas it's built from) when `inferred: true`.
39
+ - `pluginFetch({ validator: { request: "zod", response: "zod" } })` — both request and response must be Zod-validated. The bare `validator: "zod"` shorthand only validates the response, which is not sufficient.
40
+
41
+ When either requirement isn't met, this plugin reports a warning and skips generating any `*.remote.ts` files, without failing the rest of the Kubb run.
42
+
43
+ ## Usage
44
+
45
+ ```ts
46
+ import { defineConfig } from "kubb/config";
47
+ import { pluginZod } from "@kubb/plugin-zod";
48
+ import { pluginFetch } from "@kubb/plugin-fetch";
49
+ import { pluginSveltekitRemoteFunctions } from "kubb-plugin-sveltekit-remote-functions";
50
+
51
+ export default defineConfig({
52
+ input: { path: "./petStore.yaml" },
53
+ output: { path: "./src/lib/generated", clean: true },
54
+ plugins: [
55
+ pluginZod({ inferred: true }),
56
+ pluginFetch({ output: { path: "./clients" }, validator: { request: "zod", response: "zod" } }),
57
+ pluginSveltekitRemoteFunctions(),
58
+ ],
59
+ });
60
+ ```
61
+
62
+ SvelteKit only allows remote-function files under `src` (not `src/lib/server`), so point this plugin's `output.path` accordingly — note the example above resolves it as a sibling of the config's own `output.path`, not `pluginFetch()`'s.
63
+
64
+ ## How the remote-function kind is chosen
65
+
66
+ By default, the HTTP method decides the kind(s) generated for an operation:
67
+
68
+ | Method | Generated kind(s) |
69
+ | -------------------------------- | ---------------------------------------------------------------- |
70
+ | `GET` | `query` |
71
+ | `POST`, `PUT`, `PATCH`, `DELETE` | `command` (plus `form` when `sveltekit.generateForms` is `true`) |
72
+
73
+ Override this per operation with the `x-sveltekit-remote-function-type` OpenAPI vendor extension, an array of any of: `"query"`, `"query.batch"`, `"query.live"`, `"command"`, `"form"`, `"prerender"`. An explicit extension always takes precedence over `sveltekit.generateForms`.
74
+
75
+ ```yaml
76
+ paths:
77
+ /pets/{petId}:
78
+ delete:
79
+ operationId: deletePet
80
+ x-sveltekit-remote-function-type: ["command"]
81
+ ```
82
+
83
+ An invalid extension value (not an array, empty, or containing an unrecognized string) throws a descriptive error naming the operation and the offending value.
84
+
85
+ ## Naming when an operation generates more than one kind
86
+
87
+ `query` and `command` are normally unsuffixed. Additional kinds get a suffix appended to the base operation name (the same camelCase name `@kubb/plugin-fetch` uses for the wrapped function):
88
+
89
+ | Kind | Suffix |
90
+ | ------------- | ------------------------------------------------------------------------------- |
91
+ | `query` | _(none, unless `command` is also generated for the same operation — see below)_ |
92
+ | `command` | _(none, unless `query` is also generated for the same operation — see below)_ |
93
+ | `form` | `Form` |
94
+ | `query.batch` | `Batch` |
95
+ | `query.live` | `Live` |
96
+ | `prerender` | _(none if the only kind, else `Prerender`)_ |
97
+
98
+ For example, `POST /pets` with `sveltekit.generateForms: true` (`command` + `form`) generates `createPet` (a `command`) and `createPetForm` (a `form`). `query` and `command` are mutually exclusive under the default method-based resolution, so there's normally no ambiguity in leaving either bare; if an explicit `x-sveltekit-remote-function-type` override lists both together for the same operation, they fall back to their own suffix (`Query`/`Command`) instead, to avoid a duplicate export.
99
+
100
+ The wrapped `@kubb/plugin-fetch` function is always imported under a `Fetch`-suffixed alias (e.g. `import { createPet as createPetFetch } from ...`), decoupled from the exported remote-function name(s), so an unsuffixed `query`/`command` export can never collide with the import of the function it wraps.
101
+
102
+ ## Explicit request/response typing
103
+
104
+ Every generated remote function's handler is explicitly annotated with the types `@kubb/plugin-zod` infers for that operation, instead of relying only on inference from the schema value passed into `query`/`command`/`form`/`prerender`/`query.batch`/`query.live`:
105
+
106
+ - The handler's input parameter(s) are typed as `<Name>OptionsSchemaType` — the combined `{ body, path, query, headers }` options type, resolved via `@kubb/plugin-zod`'s public `resolver.response.options(node)` accessor.
107
+ - The handler's return value(s) are typed as `<Name>ResponseSchemaType` — the union of the operation's success responses, resolved via `resolver.response.response(node)` (the schema's value name) combined with `resolver.schema.type(name)`.
108
+
109
+ For example:
110
+
111
+ ```ts
112
+ export const createPet = command(createPetOptionsSchema, async (input: CreatePetOptionsSchemaType): Promise<CreatePetResponseSchemaType> => {
113
+ const { fetch } = getRequestEvent();
114
+ const transport = createSvelteKitTransport(fetch);
115
+ return createPetFetch({ ...input, transport }).unwrap();
116
+ });
117
+ ```
118
+
119
+ `query.batch` and `query.live` annotate every intermediate value too (the `inputs` array, the `results` array, and the returned per-item mapper's own parameter/return types for `query.batch`; the generator function's parameter and its `AsyncGenerator<...>` return type for `query.live`).
120
+
121
+ This doesn't change runtime behavior — SvelteKit already infers these same types from the schema value — but it turns any drift between what `@kubb/plugin-zod` infers and what the wrapped `@kubb/plugin-fetch` function actually expects/returns into an immediate, readable compile error in the generated file, and makes each `.remote.ts` file self-documenting without having to trace through the schema value to see its shape.
122
+
123
+ ## Options
124
+
125
+ This plugin's `Options` type extends `@kubb/plugin-fetch`'s own `Options` minus `validator` (`output`, `include`, `exclude`, `override`, `baseURL`, `group`, `sdk`, `resolver`), so configuring it feels the same as configuring `pluginFetch()`, plus one plugin-specific field:
126
+
127
+ - `sveltekit.liveQueryPollIntervalMs?: number` — poll interval (ms) used by the generated `query.live` async generator body. Default `5000`.
128
+ - `sveltekit.generateForms?: boolean` — controls the **default**, method-based kind resolution: when `false` (the default), `POST | PUT | PATCH | DELETE` operations generate just `command`; when `true`, they generate both `command` and `form`. Has no effect on operations that declare the `x-sveltekit-remote-function-type` extension — the extension always wins.
129
+
130
+ Notes on the reused fields, since this plugin wraps `@kubb/plugin-fetch`'s functions rather than performing HTTP calls itself:
131
+
132
+ - `output` — where this plugin's own `*.remote.ts` files are written; independent of the `pluginFetch()` instance's own `output`.
133
+ - `group` — defaults to `{ type: 'tag' }` (unlike `@kubb/plugin-fetch`, which defaults to no grouping), so that two operations that share an `operationId` but live under different tags resolve to different output paths instead of silently colliding into a single file with duplicate exports. Override to `null`/`undefined` for a flat `output.path` instead, but only if you're confident every operation's `operationId` is unique across the whole spec.
134
+ - `validator` — omitted entirely, unlike `@kubb/plugin-fetch`'s `Options`. Every remote function always validates its argument against the operation's `@kubb/plugin-zod` "options" schema; there is no "unchecked" mode. See [Requirements](#requirements).
135
+ - `baseURL`, `sdk` — accepted for shape parity but are no-ops: the wrapped call already carries its own `baseURL`, and remote functions are always standalone exports, never a class-based SDK.
136
+
137
+ ## Extension points
138
+
139
+ - **Custom transport** (`templates/sveltekitTransport.ts`, copied into `.kubb/sveltekitTransport.ts` next to `@kubb/plugin-fetch`'s own `.kubb/client.ts`): `createSvelteKitTransport(fetch)` builds a `Transport` that sends requests through the given `fetch` implementation — every generated remote function calls it with `getRequestEvent().fetch` so requests reuse the incoming request's credentials/origin. Copy and modify this file if you need different request-building behavior.
140
+
141
+ ## Known limitations
142
+
143
+ - `form`'s schema is the same combined `{ body, path, query, headers }` options schema used by every other kind (not just the raw request body), since SvelteKit's `form` handler receives the parsed argument object directly and that object is exactly what the wrapped fetch call expects. Submitted form fields must therefore be named/nested to match that shape (e.g. `body.name`, `path.petId`), not just the body's own fields.
144
+ - SvelteKit's `form()` is stricter than `command()`/`query()` about the schema's inferred shape (e.g. every boolean must be optional). Operations whose request body is modeled as a Zod `union` of several content-type variants (common for specs that declare the same body under multiple content types) can fail this stricter `form()` type-check even though `command()` for the same operation compiles fine — a pre-existing consequence of how `@kubb/plugin-zod` models those bodies, not something this plugin's schema selection introduces.
145
+ - `query.batch` does not perform true HTTP-level batching — it satisfies SvelteKit's `(input, index) => output` batching contract by calling the wrapped operation once per input.
146
+ - `query.live` generates a basic polling implementation (`sveltekit.liveQueryPollIntervalMs`), not push-based live data; customize the generated file for real-time use cases.
147
+ - `prerender`'s per-operation `inputs`/`dynamic` options are not currently generated.
148
+
149
+ ## License
150
+
151
+ [MIT](../LICENSE)
152
+
153
+ <!-- Badges -->
154
+
155
+ [npm-version-src]: https://img.shields.io/npm/v/kubb-plugin-sveltekit-remote-functions.svg
156
+ [npm-version-href]: https://www.npmjs.com/package/kubb-plugin-sveltekit-remote-functions
157
+ [npm-downloads-src]: https://img.shields.io/npm/dm/kubb-plugin-sveltekit-remote-functions.svg
158
+ [npm-downloads-href]: https://www.npmjs.com/package/kubb-plugin-sveltekit-remote-functions
159
+ [license-src]: https://img.shields.io/npm/l/kubb-plugin-sveltekit-remote-functions.svg
160
+ [license-href]: ../LICENSE
@@ -0,0 +1,9 @@
1
+ import { type Generator } from "kubb/kit";
2
+ import type { PluginSveltekitRemoteFunctions } from "../types.js";
3
+ /**
4
+ * Core generator for `kubb-plugin-sveltekit-remote-functions`. For each HTTP operation, resolves
5
+ * the SvelteKit remote-function kind(s) it should generate (method-based, or overridden by
6
+ * `x-sveltekit-remote-function-type`), locates the `@kubb/plugin-fetch` function it wraps, and
7
+ * emits one `<operationName>.remote.ts` file per operation containing every resolved kind.
8
+ */
9
+ export declare const remoteFunctionGenerator: Generator<PluginSveltekitRemoteFunctions>;
@@ -0,0 +1,173 @@
1
+ import path from "node:path";
2
+ import { ast } from "kubb/kit";
3
+ import { resolveRemoteFunctionKinds } from "../kindResolution.js";
4
+ import { remoteFunctionName } from "../naming.js";
5
+ import { checkPreconditions, resolveFetchOperation, resolveZodOperation } from "../resolveOperations.js";
6
+ /**
7
+ * Converts an absolute file path into a relative ESM import specifier (POSIX separators, no
8
+ * extension, `./`-prefixed for sibling files), computed from the importing file's own directory.
9
+ */
10
+ function toImportSpecifier(fromFilePath, toFilePath) {
11
+ const relative = path.relative(path.dirname(fromFilePath), toFilePath);
12
+ const withoutExt = relative.replace(/\.[cm]?tsx?$/, "");
13
+ const posix = withoutExt.split(path.sep).join("/");
14
+ return posix.startsWith(".") ? posix : `./${posix}`;
15
+ }
16
+ const svelteImportByKind = {
17
+ query: "query",
18
+ "query.batch": "query",
19
+ "query.live": "query",
20
+ command: "command",
21
+ form: "form",
22
+ prerender: "prerender",
23
+ };
24
+ /**
25
+ * Builds the *.remote.ts source for one remote-function kind of an operation.
26
+ */
27
+ function buildRemoteFunctionSource(options) {
28
+ const { kind, exportName, fetchName, argumentSchemaExpression, optionsSchemaTypeName, responseSchemaTypeName, pollIntervalMs, resultAccessor } = options;
29
+ switch (kind) {
30
+ case "query":
31
+ case "command":
32
+ case "form":
33
+ case "prerender":
34
+ return `export const ${exportName} = ${kind}(${argumentSchemaExpression}, async (input: ${optionsSchemaTypeName}): Promise<${responseSchemaTypeName}> => {
35
+ const { fetch } = getRequestEvent()
36
+ const transport = createSvelteKitTransport(fetch)
37
+ return ${fetchName}({ ...input, transport })${resultAccessor}
38
+ })`;
39
+ case "query.batch":
40
+ // No true HTTP batching: each input still triggers its own call to the wrapped
41
+ // \`@kubb/plugin-fetch\` function, deduped only at the SvelteKit layer.
42
+ return `export const ${exportName} = query.batch(${argumentSchemaExpression}, async (inputs: ${optionsSchemaTypeName}[]) => {
43
+ const { fetch } = getRequestEvent()
44
+ const transport = createSvelteKitTransport(fetch)
45
+ const results: ${responseSchemaTypeName}[] = await Promise.all(
46
+ inputs.map((input: ${optionsSchemaTypeName}) => ${fetchName}({ ...input, transport })${resultAccessor}),
47
+ )
48
+ return (_input: ${optionsSchemaTypeName}, index: number): ${responseSchemaTypeName} => results[index]
49
+ })`;
50
+ case "query.live":
51
+ // Basic polling default; true push-based live data needs bespoke server logic.
52
+ return `export const ${exportName} = query.live(${argumentSchemaExpression}, async function* (input: ${optionsSchemaTypeName}): AsyncGenerator<${responseSchemaTypeName}> {
53
+ const { fetch } = getRequestEvent()
54
+ const transport = createSvelteKitTransport(fetch)
55
+ while (true) {
56
+ yield await ${fetchName}({ ...input, transport })${resultAccessor}
57
+ await new Promise((resolve) => setTimeout(resolve, ${pollIntervalMs}))
58
+ }
59
+ })`;
60
+ }
61
+ }
62
+ /**
63
+ * Core generator for `kubb-plugin-sveltekit-remote-functions`. For each HTTP operation, resolves
64
+ * the SvelteKit remote-function kind(s) it should generate (method-based, or overridden by
65
+ * `x-sveltekit-remote-function-type`), locates the `@kubb/plugin-fetch` function it wraps, and
66
+ * emits one `<operationName>.remote.ts` file per operation containing every resolved kind.
67
+ */
68
+ export const remoteFunctionGenerator = {
69
+ name: "sveltekit-remote-functions",
70
+ match(node) {
71
+ return node.kind === "Operation" && ast.isHttpOperationNode(node);
72
+ },
73
+ operation(node, ctx) {
74
+ if (!ast.isHttpOperationNode(node)) {
75
+ return undefined;
76
+ }
77
+ const kinds = resolveRemoteFunctionKinds(node, ctx.adapter.document, ctx.plugin.options?.sveltekit?.generateForms ?? false);
78
+ if (kinds.length === 0) {
79
+ return undefined;
80
+ }
81
+ // Hard requirements: `@kubb/plugin-zod` must be configured with `inferred: true` and
82
+ // `@kubb/plugin-fetch` with `validator: { request: 'zod', response: 'zod' }`. Skip generating
83
+ // anything (with a one-time warning) rather than failing the build when either is missing.
84
+ if (!checkPreconditions(ctx)) {
85
+ return undefined;
86
+ }
87
+ const fetchOp = resolveFetchOperation(node, ctx);
88
+ const zodOp = resolveZodOperation(node, ctx);
89
+ const root = path.resolve(ctx.config.root, ctx.config.output.path);
90
+ const file = ctx.resolver.file({
91
+ name: node.operationId,
92
+ extname: ".remote.ts",
93
+ tag: node.tags[0] ?? "default",
94
+ path: node.path,
95
+ root,
96
+ output: ctx.options.output,
97
+ group: ctx.options.group ?? undefined,
98
+ });
99
+ const baseName = ctx.resolver.name(node.operationId);
100
+ // Import the wrapped `@kubb/plugin-fetch` function under a fixed `Fetch`-suffixed alias,
101
+ // decoupled from whatever the remote-function export name(s) turn out to be. `Fetch` is never
102
+ // one of this plugin's own kind suffixes (`Query`/`Command`/`Form`/`Batch`/`Live`/`Prerender`),
103
+ // so the alias can never collide with an exported remote function in the same file — e.g. a
104
+ // `command` kind that resolves to the unsuffixed base name would otherwise clash with an
105
+ // import of the same name.
106
+ const fetchImportAlias = `${fetchOp.name}Fetch`;
107
+ const svelteImports = new Set(["getRequestEvent"]);
108
+ for (const kind of kinds) {
109
+ svelteImports.add(svelteImportByKind[kind]);
110
+ }
111
+ // The combined `{ body, path, query, headers }` options schema `@kubb/plugin-zod` generates
112
+ // (only when `inferred: true`, guaranteed by `checkPreconditions`) is used directly, unmodified,
113
+ // as the argument schema for every kind, `form` included: it's exactly the shape the wrapped
114
+ // `@kubb/plugin-fetch` function's own `options` argument expects.
115
+ const optionsSchemaExpression = zodOp.optionsSchemaName;
116
+ const sources = [];
117
+ // `.unwrap()` only exists when the wrapped `pluginFetch()` instance uses its default
118
+ // `returnType: 'full'`. With `returnType: 'data'` the wrapped call already resolves to the
119
+ // bare success body directly (see `FetchOperation.returnType`'s doc comment), so calling
120
+ // `.unwrap()` on it would be a compile error.
121
+ const resultAccessor = fetchOp.returnType === "data" ? "" : ".unwrap()";
122
+ for (const kind of kinds) {
123
+ const exportName = remoteFunctionName(baseName, kind, kinds);
124
+ sources.push(buildRemoteFunctionSource({
125
+ kind,
126
+ exportName,
127
+ fetchName: fetchImportAlias,
128
+ argumentSchemaExpression: optionsSchemaExpression,
129
+ optionsSchemaTypeName: zodOp.optionsSchemaTypeName,
130
+ responseSchemaTypeName: zodOp.responseSchemaTypeName,
131
+ pollIntervalMs: ctx.options.sveltekit.liveQueryPollIntervalMs,
132
+ resultAccessor,
133
+ }));
134
+ }
135
+ const kubbDir = path.dirname(fetchOp.clientPath);
136
+ const sveltekitTransportPath = path.join(kubbDir, "sveltekitTransport.ts");
137
+ const imports = [
138
+ ast.factory.createImport({
139
+ name: Array.from(svelteImports),
140
+ path: "$app/server",
141
+ }),
142
+ ast.factory.createImport({
143
+ name: [{ propertyName: fetchOp.name, name: fetchImportAlias }],
144
+ path: toImportSpecifier(file.path, fetchOp.path),
145
+ }),
146
+ ast.factory.createImport({
147
+ name: ["createSvelteKitTransport"],
148
+ path: toImportSpecifier(file.path, sveltekitTransportPath),
149
+ }),
150
+ ast.factory.createImport({
151
+ name: [zodOp.optionsSchemaName],
152
+ path: toImportSpecifier(file.path, zodOp.path),
153
+ }),
154
+ ast.factory.createImport({
155
+ name: [zodOp.optionsSchemaTypeName, zodOp.responseSchemaTypeName],
156
+ path: toImportSpecifier(file.path, zodOp.path),
157
+ isTypeOnly: true,
158
+ }),
159
+ ];
160
+ const fileNode = ast.factory.createFile({
161
+ baseName: file.baseName,
162
+ path: file.path,
163
+ imports,
164
+ sources: [
165
+ ast.factory.createSource({
166
+ nodes: sources.map((source) => ast.factory.createText(source)),
167
+ }),
168
+ ],
169
+ exports: [],
170
+ });
171
+ return [fileNode];
172
+ },
173
+ };
@@ -0,0 +1,3 @@
1
+ export { pluginSveltekitRemoteFunctions, default } from "./plugin.js";
2
+ export { pluginSveltekitRemoteFunctionsName, remoteFunctionTypeExtensionKey } from "./types.js";
3
+ export type { Options, PluginSveltekitRemoteFunctions, RemoteFunctionKind, ResolvedOptions, SveltekitOptions } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { pluginSveltekitRemoteFunctions, default } from "./plugin.js";
2
+ export { pluginSveltekitRemoteFunctionsName, remoteFunctionTypeExtensionKey } from "./types.js";
@@ -0,0 +1,29 @@
1
+ import type { ast } from "kubb/kit";
2
+ import { type RemoteFunctionKind } from "./types.js";
3
+ /**
4
+ * Minimal, defensive shape of the parsed OpenAPI document needed to read vendor extensions off an
5
+ * operation. `GeneratorContext['adapter']['document']` is typed `unknown` at the `@kubb/core`
6
+ * level (the concrete `Document` type lives in the adapter package, e.g. `@kubb/adapter-oas`), so
7
+ * this plugin reads through a loose, structurally-typed view instead of depending on the adapter
8
+ * package directly.
9
+ */
10
+ export type LooseOpenApiDocument = {
11
+ paths?: Record<string, Record<string, Record<string, unknown>> | undefined>;
12
+ };
13
+ /**
14
+ * Reads the raw operation object (the `get`/`post`/... entry on a path item, including vendor
15
+ * extensions) for an `HttpOperationNode` out of the adapter's parsed document.
16
+ */
17
+ export declare function getRawOperationObject(node: ast.HttpOperationNode, document: unknown): Record<string, unknown> | undefined;
18
+ /**
19
+ * Resolves which SvelteKit remote-function kinds an operation should generate.
20
+ *
21
+ * - If the operation carries `x-sveltekit-remote-function-type`, that array is used as-is (after
22
+ * validating every entry and de-duplicating) — regardless of `generateForms`.
23
+ * - Otherwise, the HTTP method decides: `GET` → `['query']`; `POST | PUT | PATCH | DELETE` →
24
+ * `['command']`, plus `'form'` when `generateForms` is `true`.
25
+ *
26
+ * Throws a descriptive error when the extension is present but malformed (not an array, empty, or
27
+ * containing an unknown value), since that is almost certainly a spec authoring mistake.
28
+ */
29
+ export declare function resolveRemoteFunctionKinds(node: ast.HttpOperationNode, document: unknown, generateForms: boolean): Array<RemoteFunctionKind>;
@@ -0,0 +1,50 @@
1
+ import { remoteFunctionTypeExtensionKey } from "./types.js";
2
+ const allKinds = new Set(["query", "query.batch", "query.live", "command", "form", "prerender"]);
3
+ function isRemoteFunctionKind(value) {
4
+ return typeof value === "string" && allKinds.has(value);
5
+ }
6
+ /**
7
+ * Reads the raw operation object (the `get`/`post`/... entry on a path item, including vendor
8
+ * extensions) for an `HttpOperationNode` out of the adapter's parsed document.
9
+ */
10
+ export function getRawOperationObject(node, document) {
11
+ const doc = document;
12
+ const method = node.method.toLowerCase();
13
+ return doc?.paths?.[node.path]?.[method];
14
+ }
15
+ /**
16
+ * Resolves which SvelteKit remote-function kinds an operation should generate.
17
+ *
18
+ * - If the operation carries `x-sveltekit-remote-function-type`, that array is used as-is (after
19
+ * validating every entry and de-duplicating) — regardless of `generateForms`.
20
+ * - Otherwise, the HTTP method decides: `GET` → `['query']`; `POST | PUT | PATCH | DELETE` →
21
+ * `['command']`, plus `'form'` when `generateForms` is `true`.
22
+ *
23
+ * Throws a descriptive error when the extension is present but malformed (not an array, empty, or
24
+ * containing an unknown value), since that is almost certainly a spec authoring mistake.
25
+ */
26
+ export function resolveRemoteFunctionKinds(node, document, generateForms) {
27
+ const raw = getRawOperationObject(node, document);
28
+ const extensionValue = raw?.[remoteFunctionTypeExtensionKey];
29
+ if (extensionValue !== undefined) {
30
+ if (!Array.isArray(extensionValue) || extensionValue.length === 0) {
31
+ throw new Error(`Operation "${node.operationId}" declares "${remoteFunctionTypeExtensionKey}" but its value is not a non-empty array (got: ${JSON.stringify(extensionValue)}).`);
32
+ }
33
+ const invalid = extensionValue.filter((value) => !isRemoteFunctionKind(value));
34
+ if (invalid.length > 0) {
35
+ throw new Error(`Operation "${node.operationId}" declares "${remoteFunctionTypeExtensionKey}" with unsupported value(s): ${JSON.stringify(invalid)}. Valid values are: ${Array.from(allKinds).join(", ")}.`);
36
+ }
37
+ return Array.from(new Set(extensionValue));
38
+ }
39
+ switch (node.method.toUpperCase()) {
40
+ case "GET":
41
+ return ["query"];
42
+ case "POST":
43
+ case "PUT":
44
+ case "PATCH":
45
+ case "DELETE":
46
+ return generateForms ? ["command", "form"] : ["command"];
47
+ default:
48
+ return [];
49
+ }
50
+ }
@@ -0,0 +1,11 @@
1
+ import type { RemoteFunctionKind } from "./types.js";
2
+ /**
3
+ * Resolves the exported identifier for one remote-function kind of an operation.
4
+ *
5
+ * @param baseName - The operation's base name, e.g. `resolver.name(operationId)` (camelCase,
6
+ * matching the name `@kubb/plugin-fetch` itself would use for the wrapped function).
7
+ * @param kind - The remote-function kind being named.
8
+ * @param allKinds - Every kind generated for this operation, used to decide whether `query`,
9
+ * `command`, or `prerender` need a suffix.
10
+ */
11
+ export declare function remoteFunctionName(baseName: string, kind: RemoteFunctionKind, allKinds: Array<RemoteFunctionKind>): string;
package/dist/naming.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Suffix applied to the base operation name for a given remote-function kind when it isn't left
3
+ * bare (e.g. `POST /pets` → `command` `createPet` + `form` `createPetForm`).
4
+ *
5
+ * `query` and `command` are normally unsuffixed ("primary"): an operation's default resolution
6
+ * never produces both for the same operation, so there is no ambiguity in leaving either bare.
7
+ * They only fall back to their own suffix (`Query`/`Command`) in the rare case where an explicit
8
+ * `x-sveltekit-remote-function-type` override lists both together (see `remoteFunctionName`).
9
+ * `prerender` is unsuffixed only when it is the sole kind generated for an operation.
10
+ */
11
+ const suffixByKind = {
12
+ query: "Query",
13
+ command: "Command",
14
+ form: "Form",
15
+ "query.batch": "Batch",
16
+ "query.live": "Live",
17
+ prerender: "Prerender",
18
+ };
19
+ /**
20
+ * Resolves the exported identifier for one remote-function kind of an operation.
21
+ *
22
+ * @param baseName - The operation's base name, e.g. `resolver.name(operationId)` (camelCase,
23
+ * matching the name `@kubb/plugin-fetch` itself would use for the wrapped function).
24
+ * @param kind - The remote-function kind being named.
25
+ * @param allKinds - Every kind generated for this operation, used to decide whether `query`,
26
+ * `command`, or `prerender` need a suffix.
27
+ */
28
+ export function remoteFunctionName(baseName, kind, allKinds) {
29
+ // `query` and `command` are mutually exclusive under the default method-based resolution, but
30
+ // an explicit extension override can list both for the same operation. In that case leaving
31
+ // both bare would produce two identically-named exports, so only the sole survivor of the pair
32
+ // stays unsuffixed.
33
+ if (kind === "query" || kind === "command") {
34
+ const otherPrimaryKind = kind === "query" ? "command" : "query";
35
+ if (!allKinds.includes(otherPrimaryKind)) {
36
+ return baseName;
37
+ }
38
+ return `${baseName}${suffixByKind[kind]}`;
39
+ }
40
+ if (kind === "prerender" && allKinds.length === 1) {
41
+ return baseName;
42
+ }
43
+ return `${baseName}${suffixByKind[kind]}`;
44
+ }
@@ -0,0 +1,48 @@
1
+ import { type Options, type PluginSveltekitRemoteFunctions } from "./types.js";
2
+ /**
3
+ * Generates SvelteKit remote functions (`query`, `query.batch`, `query.live`, `command`, `form`,
4
+ * `prerender`) from an OpenAPI spec, one per operation, wrapping the async function
5
+ * `@kubb/plugin-fetch` already generates for that operation.
6
+ *
7
+ * The HTTP method decides which remote-function kind(s) an operation generates by default
8
+ * (`GET` → `query`; `POST | PUT | PATCH | DELETE` → `command`, plus `form` when
9
+ * `sveltekit.generateForms` is `true`), overridable per operation with the
10
+ * `x-sveltekit-remote-function-type` OpenAPI extension (an array of
11
+ * `'query' | 'query.batch' | 'query.live' | 'command' | 'form' | 'prerender'`), which always takes
12
+ * precedence over `sveltekit.generateForms`.
13
+ *
14
+ * Requires `pluginFetch()` and `pluginZod()` in the same pipeline (both are hard, driver-enforced
15
+ * dependencies). Every generated remote function uses the operation's `@kubb/plugin-zod`
16
+ * "options" schema directly as its argument schema, which requires two additional configuration
17
+ * requirements on those plugins:
18
+ * - `pluginZod({ inferred: true })` — `@kubb/plugin-zod` only generates the combined options
19
+ * schema in that mode.
20
+ * - `pluginFetch({ validator: { request: 'zod', response: 'zod' } })` — both request and response
21
+ * must be Zod-validated (the bare `validator: 'zod'` shorthand only validates the response).
22
+ *
23
+ * When either requirement isn't met, this plugin warns and skips generating any `*.remote.ts`
24
+ * files, without failing the rest of the build.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { defineConfig } from 'kubb/config'
29
+ * import { pluginTs } from '@kubb/plugin-ts'
30
+ * import { pluginZod } from '@kubb/plugin-zod'
31
+ * import { pluginFetch } from '@kubb/plugin-fetch'
32
+ * import { pluginSveltekitRemoteFunctions } from 'kubb-plugin-sveltekit-remote-functions'
33
+ *
34
+ * export default defineConfig({
35
+ * input: './petStore.yaml',
36
+ * output: { path: './src/gen' },
37
+ * plugins: [
38
+ * // Required by `pluginFetch()` itself (a hard dependency of its own) — not by this plugin.
39
+ * pluginTs(),
40
+ * pluginZod({ inferred: true }),
41
+ * pluginFetch({ output: { path: './clients' }, validator: { request: 'zod', response: 'zod' } }),
42
+ * pluginSveltekitRemoteFunctions({ output: { path: '../remote' } }),
43
+ * ],
44
+ * })
45
+ * ```
46
+ */
47
+ export declare const pluginSveltekitRemoteFunctions: (options?: Options | undefined) => import("kubb/kit").Plugin<PluginSveltekitRemoteFunctions>;
48
+ export default pluginSveltekitRemoteFunctions;