@rc312/tss-effect-serverfn 0.0.0-stage → 0.1.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -2
  3. package/dist/bootstrap.server.d.ts +12 -0
  4. package/dist/bootstrap.server.d.ts.map +1 -0
  5. package/dist/bootstrap.server.js +17 -0
  6. package/dist/bootstrap.server.js.map +1 -0
  7. package/dist/boundary.server.d.ts +26 -0
  8. package/dist/boundary.server.d.ts.map +1 -0
  9. package/dist/boundary.server.js +117 -0
  10. package/dist/boundary.server.js.map +1 -0
  11. package/dist/compiler-runtime.d.ts +40 -0
  12. package/dist/compiler-runtime.d.ts.map +1 -0
  13. package/dist/compiler-runtime.js +54 -0
  14. package/dist/compiler-runtime.js.map +1 -0
  15. package/dist/create-effect-server-fn.d.ts +84 -0
  16. package/dist/create-effect-server-fn.d.ts.map +1 -0
  17. package/dist/create-effect-server-fn.js +27 -0
  18. package/dist/create-effect-server-fn.js.map +1 -0
  19. package/dist/effect-middleware.d.ts +116 -0
  20. package/dist/effect-middleware.d.ts.map +1 -0
  21. package/dist/effect-middleware.js +46 -0
  22. package/dist/effect-middleware.js.map +1 -0
  23. package/dist/execution.d.ts +35 -0
  24. package/dist/execution.d.ts.map +1 -0
  25. package/dist/execution.js +7 -0
  26. package/dist/execution.js.map +1 -0
  27. package/dist/failure.server.d.ts +44 -0
  28. package/dist/failure.server.d.ts.map +1 -0
  29. package/dist/failure.server.js +161 -0
  30. package/dist/failure.server.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +3 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/provenance.server.d.ts +54 -0
  36. package/dist/provenance.server.d.ts.map +1 -0
  37. package/dist/provenance.server.js +87 -0
  38. package/dist/provenance.server.js.map +1 -0
  39. package/dist/server.d.ts +3 -0
  40. package/dist/server.d.ts.map +1 -0
  41. package/dist/server.js +3 -0
  42. package/dist/server.js.map +1 -0
  43. package/dist/vite.d.ts +24 -0
  44. package/dist/vite.d.ts.map +1 -0
  45. package/dist/vite.js +296 -0
  46. package/dist/vite.js.map +1 -0
  47. package/docs/composition.md +188 -0
  48. package/docs/usage.md +223 -0
  49. package/package.json +67 -4
  50. package/src/bootstrap.server.ts +22 -0
  51. package/src/boundary.server.ts +198 -0
  52. package/src/compiler-runtime.ts +75 -0
  53. package/src/create-effect-server-fn.ts +197 -0
  54. package/src/effect-middleware.ts +171 -0
  55. package/src/execution.ts +39 -0
  56. package/src/failure.server.ts +257 -0
  57. package/src/index.ts +12 -0
  58. package/src/provenance.server.ts +125 -0
  59. package/src/server.ts +7 -0
  60. package/src/vite.ts +462 -0
package/dist/vite.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Plugin } from "vite";
2
+ /**
3
+ * Adapt Effect Schemas and handlers before TanStack Start compiles server functions.
4
+ *
5
+ * Install before `tanstackStart()`. The plugin recognizes static builder chains
6
+ * and transparent aliases/re-exports, wraps validator and handler arguments, and
7
+ * emits source maps. Start remains responsible for extraction, RPC identifiers,
8
+ * manifests, and removal of server implementations from browser bundles.
9
+ *
10
+ * @returns A Vite pre-transform plugin with build/watch module-cache invalidation.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { tanstackStart } from "@tanstack/react-start/plugin/vite";
15
+ * import { effectServerFns } from "@rc312/tss-effect-serverfn/vite";
16
+ * import { defineConfig } from "vite";
17
+ *
18
+ * export default defineConfig({
19
+ * plugins: [effectServerFns(), tanstackStart()],
20
+ * });
21
+ * ```
22
+ */
23
+ export declare const effectServerFns: () => Plugin;
24
+ //# sourceMappingURL=vite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAuWnC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,eAAe,QAAO,MA6ElC,CAAC"}
package/dist/vite.js ADDED
@@ -0,0 +1,296 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import * as babel from "@babel/core";
3
+ import * as t from "@babel/types";
4
+ const runtimeModule = "@rc312/tss-effect-serverfn/compiler-runtime";
5
+ const builderMethods = new Set(["middleware", "validator", "handler"]);
6
+ const staticName = (node) => {
7
+ if (t.isIdentifier(node)) {
8
+ return node.name;
9
+ }
10
+ return t.isStringLiteral(node) ? node.value : undefined;
11
+ };
12
+ const parseModule = async (code, id) => {
13
+ const ast = await babel.parseAsync(code, {
14
+ babelrc: false,
15
+ configFile: false,
16
+ filename: id,
17
+ parserOpts: { plugins: ["typescript", "jsx"] },
18
+ sourceType: "module",
19
+ });
20
+ let program;
21
+ if (ast) {
22
+ babel.traverse(ast, {
23
+ Program(path) {
24
+ program = path;
25
+ path.stop();
26
+ },
27
+ });
28
+ }
29
+ if (!ast || !program) {
30
+ throw new Error(`Unable to parse Effect server functions in ${id}`);
31
+ }
32
+ return { ast, program };
33
+ };
34
+ /**
35
+ * Resolve whether an expression originates from `createEffectServerFn`.
36
+ * Follows lexical bindings, configured builders, and application re-exports;
37
+ * Babel NodePaths preserve scope so shadowed names are not mistaken for imports.
38
+ * Traversal-local visited keys terminate alias and export cycles.
39
+ *
40
+ * @param load - Load an application module with its binding-aware Program path.
41
+ * @param resolve - Resolve imports through Vite's module-resolution rules.
42
+ * @returns An asynchronous predicate over expressions in a module.
43
+ * @throws {Error} When a recognized factory uses unsupported mutable or dynamic composition.
44
+ */
45
+ const createFactoryResolver = (load, resolve) => {
46
+ const trace = {
47
+ async factory(path, id, seen = new Set()) {
48
+ if (path.isTSAsExpression()) {
49
+ return trace.factory(path.get("expression"), id, seen);
50
+ }
51
+ if (path.isTSSatisfiesExpression()) {
52
+ return trace.factory(path.get("expression"), id, seen);
53
+ }
54
+ if (path.isTSNonNullExpression()) {
55
+ return trace.factory(path.get("expression"), id, seen);
56
+ }
57
+ if (path.isCallExpression()) {
58
+ return trace.factory(path.get("callee"), id, seen);
59
+ }
60
+ if (path.isMemberExpression()) {
61
+ return trace.member(path, id, seen);
62
+ }
63
+ if (path.isImportSpecifier() && path.parentPath.isImportDeclaration()) {
64
+ const { imported } = path.node;
65
+ return trace.imported(path.parentPath.node.source.value, t.isIdentifier(imported) ? imported.name : imported.value, id, seen);
66
+ }
67
+ if (path.isVariableDeclarator()) {
68
+ return trace.factory(path.get("init"), id, seen);
69
+ }
70
+ if (path.isArrowFunctionExpression() &&
71
+ (await trace.factory(path.get("body"), id, seen))) {
72
+ throw new Error(`Effect factory wrappers are not supported in ${id}; export a configured builder directly`);
73
+ }
74
+ if (!path.isIdentifier()) {
75
+ return false;
76
+ }
77
+ const binding = path.scope.getBinding(path.node.name);
78
+ const key = `${id}:${binding?.scope.uid}:${path.node.name}`;
79
+ if (!binding || seen.has(key)) {
80
+ return false;
81
+ }
82
+ const found = await trace.factory(binding.path, id, new Set([...seen, key]));
83
+ if (found && !binding.constant) {
84
+ throw new Error(`Mutable Effect factory binding ${path.node.name} in ${id}; use const`);
85
+ }
86
+ return found;
87
+ },
88
+ async imported(source, name, importer, seen) {
89
+ if (source === "@rc312/tss-effect-serverfn") {
90
+ return name === "createEffectServerFn";
91
+ }
92
+ const id = await resolve(source, importer);
93
+ if (!id ||
94
+ id.includes("/node_modules/") ||
95
+ id.startsWith("\0") ||
96
+ seen.has(`${id}:${name}`)) {
97
+ return false;
98
+ }
99
+ const next = new Set([...seen, `${id}:${name}`]);
100
+ const { program } = await load(id);
101
+ const stars = [];
102
+ for (const statement of program.get("body")) {
103
+ if (statement.isExportAllDeclaration()) {
104
+ stars.push(statement.node.source.value);
105
+ }
106
+ if (!statement.isExportNamedDeclaration()) {
107
+ continue;
108
+ }
109
+ for (const specifier of statement.get("specifiers")) {
110
+ if (specifier.isExportSpecifier() &&
111
+ staticName(specifier.node.exported) === name) {
112
+ return statement.node.source
113
+ ? trace.imported(statement.node.source.value, specifier.node.local.name, id, next)
114
+ : trace.factory(specifier.get("local"), id, next);
115
+ }
116
+ }
117
+ const declaration = statement.get("declaration");
118
+ if (declaration.isVariableDeclaration()) {
119
+ const exported = declaration
120
+ .get("declarations")
121
+ .find((item) => item.get("id").isIdentifier({ name }));
122
+ if (exported) {
123
+ return trace.factory(exported.get("id"), id, next);
124
+ }
125
+ }
126
+ }
127
+ const matches = await Promise.all(stars.map((star) => trace.imported(star, name, id, next)));
128
+ return matches.some(Boolean);
129
+ },
130
+ async member(path, id, seen) {
131
+ const object = path.get("object");
132
+ if (await trace.factory(object, id, seen)) {
133
+ if (path.node.computed ||
134
+ !t.isIdentifier(path.node.property) ||
135
+ !builderMethods.has(path.node.property.name)) {
136
+ throw new Error(`Unsupported Effect builder composition in ${id}; use static .middleware(), .validator(), and .handler() calls`);
137
+ }
138
+ return true;
139
+ }
140
+ const binding = object.isIdentifier()
141
+ ? object.scope.getBinding(object.node.name)
142
+ : undefined;
143
+ if (!path.node.computed &&
144
+ t.isIdentifier(path.node.property) &&
145
+ binding?.path.isImportNamespaceSpecifier() &&
146
+ binding.path.parentPath.isImportDeclaration()) {
147
+ return trace.imported(binding.path.parentPath.node.source.value, path.node.property.name, id, seen);
148
+ }
149
+ return false;
150
+ },
151
+ };
152
+ return trace.factory;
153
+ };
154
+ /** Check helper import identity, rather than its local name, to keep lowering idempotent. */
155
+ const isAlreadyLowered = (argument, helper) => {
156
+ if (!argument.isCallExpression() || !t.isIdentifier(argument.node.callee)) {
157
+ return false;
158
+ }
159
+ const binding = argument.scope.getBinding(argument.node.callee.name);
160
+ return (binding?.path.isImportSpecifier() &&
161
+ t.isIdentifier(binding.path.node.imported, { name: helper }) &&
162
+ binding.path.parentPath.isImportDeclaration() &&
163
+ binding.path.parentPath.node.source.value === runtimeModule);
164
+ };
165
+ /** Collect syntactic candidates; binding analysis must approve them before rewriting. */
166
+ const collectBuilderCalls = (program) => {
167
+ const candidates = [];
168
+ program.traverse({
169
+ CallExpression(path) {
170
+ const { callee } = path.node;
171
+ if (t.isMemberExpression(callee) &&
172
+ ["validator", "handler"].includes(staticName(callee.property) ?? "")) {
173
+ candidates.push(path);
174
+ }
175
+ },
176
+ });
177
+ return candidates;
178
+ };
179
+ /**
180
+ * Wrap validated builder arguments with generated runtime helpers in place.
181
+ * Input paths must already be resolved to Effect builders. Fresh import names
182
+ * avoid lexical collisions; previously lowered arguments remain untouched.
183
+ *
184
+ * @returns Whether any AST nodes and helper imports were added or changed.
185
+ * @throws {Error} For computed methods or calls without exactly one expression argument.
186
+ */
187
+ const lowerBuilderCalls = (calls, program, id) => {
188
+ const helpers = new Map();
189
+ for (const call of calls) {
190
+ const { callee } = call.node;
191
+ if (!t.isMemberExpression(callee) ||
192
+ callee.computed ||
193
+ !t.isIdentifier(callee.property)) {
194
+ throw new Error(`Computed Effect builder methods are unsupported in ${id}`);
195
+ }
196
+ const [argument] = call.get("arguments");
197
+ if (call.node.arguments.length !== 1 || !argument?.isExpression()) {
198
+ throw new Error(`Effect builders require one schema or handler expression before TanStack compilation in ${id}`);
199
+ }
200
+ const helper = callee.property.name === "validator" ? "prepareInput" : "effectHandler";
201
+ if (isAlreadyLowered(argument, helper)) {
202
+ continue;
203
+ }
204
+ let local = helpers.get(helper);
205
+ if (!local) {
206
+ local = program.scope.generateUidIdentifier(helper);
207
+ helpers.set(helper, local);
208
+ }
209
+ argument.replaceWith(t.callExpression(local, [argument.node]));
210
+ }
211
+ if (helpers.size === 0) {
212
+ return false;
213
+ }
214
+ program.unshiftContainer("body", t.importDeclaration(Array.from(helpers, ([name, local]) => t.importSpecifier(local, t.identifier(name))), t.stringLiteral(runtimeModule)));
215
+ return true;
216
+ };
217
+ /**
218
+ * Adapt Effect Schemas and handlers before TanStack Start compiles server functions.
219
+ *
220
+ * Install before `tanstackStart()`. The plugin recognizes static builder chains
221
+ * and transparent aliases/re-exports, wraps validator and handler arguments, and
222
+ * emits source maps. Start remains responsible for extraction, RPC identifiers,
223
+ * manifests, and removal of server implementations from browser bundles.
224
+ *
225
+ * @returns A Vite pre-transform plugin with build/watch module-cache invalidation.
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * import { tanstackStart } from "@tanstack/react-start/plugin/vite";
230
+ * import { effectServerFns } from "@rc312/tss-effect-serverfn/vite";
231
+ * import { defineConfig } from "vite";
232
+ *
233
+ * export default defineConfig({
234
+ * plugins: [effectServerFns(), tanstackStart()],
235
+ * });
236
+ * ```
237
+ */
238
+ export const effectServerFns = () => {
239
+ const modules = new Map();
240
+ const load = (id) => {
241
+ let module = modules.get(id);
242
+ if (!module) {
243
+ module = (async () => parseModule(await readFile(id, "utf-8"), id))();
244
+ modules.set(id, module);
245
+ }
246
+ return module;
247
+ };
248
+ return {
249
+ buildStart() {
250
+ modules.clear();
251
+ },
252
+ enforce: "pre",
253
+ name: "@rc312/tss-effect-serverfn",
254
+ async transform(code, id) {
255
+ if (!/\.[cm]?[jt]sx?(?:\?|$)/u.test(id) ||
256
+ !/(?:validator|handler)/u.test(code)) {
257
+ return;
258
+ }
259
+ const cleanId = id.split("?")[0] ?? id;
260
+ const { ast, program } = await parseModule(code, cleanId);
261
+ const candidates = collectBuilderCalls(program);
262
+ const isFactory = createFactoryResolver(load, async (source, importer) => {
263
+ const resolved = await this.resolve(source, importer, {
264
+ skipSelf: true,
265
+ });
266
+ return resolved?.id;
267
+ });
268
+ // Finish binding analysis before mutating paths shared by nested builder calls.
269
+ const resolvedCalls = await Promise.all(candidates.map(async (candidate) => {
270
+ const callee = candidate.get("callee");
271
+ return callee.isMemberExpression() &&
272
+ (await isFactory(callee.get("object"), cleanId))
273
+ ? candidate
274
+ : undefined;
275
+ }));
276
+ const effectCalls = resolvedCalls.filter((call) => call !== undefined);
277
+ if (!lowerBuilderCalls(effectCalls, program, id)) {
278
+ return;
279
+ }
280
+ const result = await babel.transformFromAstAsync(ast, code, {
281
+ babelrc: false,
282
+ configFile: false,
283
+ filename: cleanId,
284
+ sourceFileName: cleanId,
285
+ sourceMaps: true,
286
+ });
287
+ if (result?.code) {
288
+ return { code: result.code, map: result.map ?? null };
289
+ }
290
+ },
291
+ watchChange() {
292
+ modules.clear();
293
+ },
294
+ };
295
+ };
296
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,188 @@
1
+ # Composing Effect handlers
2
+
3
+ Handlers can return any native Effect. `Effect.gen`, `pipe`, `.pipe`, `flow`, `Effect.fn`, and do notation all use the same configured runtime, decoded input, and request Scope.
4
+
5
+ Start with the [configured factory](../README.md#quick-start). Try the examples in the [basic app](https://github.com/RyanCallahan312/tss-effect-serverfn/tree/main/examples/basic): its **Composition** selector runs five equivalent server functions through real RPCs.
6
+
7
+ ## Choose a composition style
8
+
9
+ | API | Use it for |
10
+ | --- | --- |
11
+ | `Effect.gen` | Sequential steps with `yield*`, local variables, and branching |
12
+ | `pipe(value, ...steps)` / `value.pipe(...steps)` | Passing a value through transformations; both forms are equivalent for Effects |
13
+ | `flow(...steps)` | Building a reusable function that composes steps left to right |
14
+ | `Effect.fn("name")(body, ...steps)` | A reusable Effect-returning function with a tracing span and optional result combinators |
15
+ | `Effect.map` | Transforming a success value with a pure function |
16
+ | `Effect.flatMap` | Running a dependent Effect and using its result |
17
+ | `Effect.tap` | Awaiting a dependent Effect while retaining the original success value |
18
+ | `Effect.as` | Replacing a successful result with a constant |
19
+ | `Effect.all` | Combining independent Effects into a tuple or record; concurrency is explicit |
20
+ | `Effect.Do`, `bind`, `let` | Building up named results: `bind` runs Effects, `let` adds pure values |
21
+ | `Effect.catchTag` | Recovering from one tagged business failure |
22
+
23
+ `pipe` and `flow` apply ordinary functions; they do not execute the Effects those functions construct. Keep side effects inside Effect constructors such as `Effect.sync`.
24
+
25
+ ## Pipe a Schema, then pipe the decoded value
26
+
27
+ Here is a complete endpoint module using the configured factory:
28
+
29
+ ```ts
30
+ // src/greeting.functions.ts
31
+ import { Effect, flow, pipe, Schema } from "effect";
32
+
33
+ import { createEffectServerFn } from "./effect-server-fn";
34
+
35
+ const Input = Schema.Struct({
36
+ count: Schema.FiniteFromString.pipe(Schema.check(Schema.isGreaterThan(0))),
37
+ name: Schema.NonEmptyString,
38
+ });
39
+
40
+ const greeting = (name: string) => Effect.succeed(`Hello, ${name}!`);
41
+ const environment = () => Effect.succeed("server");
42
+
43
+ export const getPipedGreeting = createEffectServerFn({ method: "GET" })
44
+ .validator(Input)
45
+ .handler(({ data }) =>
46
+ pipe(
47
+ greeting(data.name),
48
+ Effect.flatMap((message) =>
49
+ environment().pipe(
50
+ Effect.map((value) => ({
51
+ count: data.count,
52
+ environment: value,
53
+ message,
54
+ }))
55
+ )
56
+ ),
57
+ Effect.withSpan("Greeting.pipe")
58
+ )
59
+ );
60
+ ```
61
+
62
+ The browser sends `{ count: "41", name: "Ada" }`. The handler receives a **number** for `data.count`; the positive-number check runs after decoding. A count of `"0"` or an invalid numeric string rejects with `INVALID_INPUT` before the handler runs.
63
+
64
+ The runnable app uses Start's native environment helpers for `greeting` and `environment`; their returned Effects compose in exactly the same way.
65
+
66
+ ## Pass `flow` directly to `.handler`
67
+
68
+ Add this to the same module:
69
+
70
+ ```ts
71
+ const greetTogether = (data: typeof Input.Type) =>
72
+ Effect.all(
73
+ {
74
+ count: Effect.succeed(data.count),
75
+ environment: environment(),
76
+ message: greeting(data.name),
77
+ },
78
+ { concurrency: 2 }
79
+ );
80
+
81
+ export const getFlowGreeting = createEffectServerFn({ method: "GET" })
82
+ .validator(Input)
83
+ .handler(
84
+ flow(
85
+ ({ data }: { data: typeof Input.Type }) => data,
86
+ greetTogether,
87
+ Effect.withSpan("Greeting.flow")
88
+ )
89
+ );
90
+ ```
91
+
92
+ Annotating the first function with `typeof Input.Type` gives a separately composed function its decoded input type. The client's input is still inferred from the Schema's encoded type. Error and required-service types continue through the composition.
93
+
94
+ `Effect.all` preserves the record's keys and bounds concurrency here to two. Use `flatMap`, `tap`, or `yield*` when one operation must finish before another starts, such as saving before reading updated state.
95
+
96
+ ## Pass `Effect.fn` directly to `.handler`
97
+
98
+ The same module can also export:
99
+
100
+ ```ts
101
+ export const getFnGreeting = createEffectServerFn({ method: "GET" })
102
+ .validator(Input)
103
+ .handler(
104
+ Effect.fn("Greeting.fn")(
105
+ function* greet({ data }: { data: typeof Input.Type }) {
106
+ return {
107
+ count: data.count,
108
+ environment: yield* environment(),
109
+ message: yield* greeting(data.name),
110
+ };
111
+ },
112
+ Effect.annotateLogs({ feature: "greeting" })
113
+ )
114
+ );
115
+ ```
116
+
117
+ `Effect.fn` accepts a generator or an Effect-returning callback. Supply its result combinators as additional arguments, as above. When calling an existing Effect-returning function, pipe the returned Effect: `greetTogether(data).pipe(Effect.withSpan("Greeting.request"))`.
118
+
119
+ ## Build a record with do notation
120
+
121
+ Another export in the same module:
122
+
123
+ ```ts
124
+ export const getDoGreeting = createEffectServerFn({ method: "GET" })
125
+ .validator(Input)
126
+ .handler(({ data }) =>
127
+ Effect.Do.pipe(
128
+ Effect.bind("message", () => greeting(data.name)),
129
+ Effect.bind("environment", () => environment()),
130
+ Effect.let("count", () => data.count)
131
+ )
132
+ );
133
+ ```
134
+
135
+ Each step retains earlier fields. A later `bind` or `let` callback can use those fields. Use `Effect.map` at the end if you want to project a different response shape.
136
+
137
+ ## Pipe service work and recover a business error
138
+
139
+ The [services app](https://github.com/RyanCallahan312/tss-effect-serverfn/tree/main/examples/services) provides `Notes` through its application-owned `ManagedRuntime`. Its `notes.functions.ts` composes the service directly:
140
+
141
+ ```ts
142
+ import { Effect, flow, Schema } from "effect";
143
+
144
+ import { createEffectServerFn } from "./effect-server-fn";
145
+ import { Notes } from "./notes.server";
146
+
147
+ const RenameInput = Schema.Struct({
148
+ id: Schema.NonEmptyString,
149
+ title: Schema.NonEmptyString,
150
+ });
151
+
152
+ const saveNote = Effect.fn("Notes.save")((data: typeof RenameInput.Type) =>
153
+ Notes.pipe(
154
+ Effect.tap((notes) => notes.rename(data.id, data.title)),
155
+ Effect.flatMap((notes) => notes.read),
156
+ Effect.map((note) => ({ saved: true, title: note.title }))
157
+ )
158
+ );
159
+
160
+ export const renameNote = createEffectServerFn({ method: "POST" })
161
+ .validator(RenameInput)
162
+ .handler(
163
+ flow(({ data }: { data: typeof RenameInput.Type }) => data, saveNote)
164
+ );
165
+
166
+ export const renameNoteIfPresent = createEffectServerFn({ method: "POST" })
167
+ .validator(RenameInput)
168
+ .handler(({ data }) =>
169
+ saveNote(data).pipe(
170
+ Effect.catchTag("NoteMissing", () =>
171
+ Effect.succeed({ saved: false, title: null })
172
+ )
173
+ )
174
+ );
175
+ ```
176
+
177
+ `tap` awaits the mutation and retains the `Notes` service, so `flatMap` can read the saved title. A failed mutation skips that read. `catchTag` implements an explicit policy: a missing note is a successful skip. In the app, enable **Recover missing notes with catchTag** and submit ID `missing` to try it.
178
+
179
+ Without recovery, `NoteMissing` reaches the configured public-error boundary. With recovery, it leaves the Effect error channel and becomes a normal successful RPC result. Defects still use the safe fallback. Endpoint Schema failures happen before this handler and cannot be caught by its pipeline.
180
+
181
+ ## Execution stays at the boundary
182
+
183
+ - Keep the static `createEffectServerFn(...).validator(...).handler(...)` declaration so Start can extract it. Compose the Schema and handler freely inside that declaration.
184
+ - Return the final Effect from the handler. The configured middleware runs it once; the client keeps Start's Promise API.
185
+ - `Effect.ensuring` finalizers and request-scoped `Effect.acquireRelease` cleanup are awaited before the handler Promise settles. Piping does not change Scope ownership.
186
+ - Compose internal Effect operations such as `saveNote` for reuse between endpoints. An exported server function is a framework/RPC boundary, not an Effect-returning domain function.
187
+
188
+ The browser suite exercises every style in dev and production preview. Type tests check encoded/decoded inputs, success values, typed recovery, and required services; boundary tests check lazy construction, `tap`, `as`, `ensuring`, and scoped cleanup on success and recovery.