@cfast/actions 0.1.2 → 0.1.3

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/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ClientDescriptor, S as Serializable } from './types-ogCcbQm3.js';
1
+ import { C as ClientDescriptor, S as Serializable } from './types-CJpjon5s.js';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import { Form } from 'react-router';
4
4
  import { ComponentProps, ReactNode } from 'react';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Grant, PermissionDescriptor } from '@cfast/permissions';
2
- import { A as ActionPermissionStatus, a as ActionServices, b as ActionsConfig, O as OperationsFn, c as ActionDefinition, d as ComposedActions } from './types-ogCcbQm3.js';
3
- export { e as ActionContext, f as ActionPermissionsMap, C as ClientDescriptor, R as RequestArgs, S as Serializable } from './types-ogCcbQm3.js';
2
+ import { A as ActionPermissionStatus, a as ActionServices, b as ActionsConfig, O as OperationsFn, c as ActionDefinition, d as ComposedActions } from './types-CJpjon5s.js';
3
+ export { e as ActionContext, f as ActionPermissionsMap, C as ClientDescriptor, D as DispatchArgs, R as RequestArgs, S as Serializable } from './types-CJpjon5s.js';
4
4
  import '@cfast/db';
5
5
 
6
6
  /**
package/dist/index.js CHANGED
@@ -104,7 +104,12 @@ function createActions(config) {
104
104
  const buildOperation = (db, input, ctx) => {
105
105
  return handler(db, input, ctx);
106
106
  };
107
- return { action, loader, client, buildOperation };
107
+ const dispatch = async (args) => {
108
+ const { ctx, input } = args;
109
+ const operation = handler(ctx.db, input, ctx);
110
+ return operation.run();
111
+ };
112
+ return { action, dispatch, loader, client, buildOperation };
108
113
  }
109
114
  function composeActions(actions) {
110
115
  const actionNames = Object.keys(actions);
@@ -80,6 +80,37 @@ type RequestArgs = {
80
80
  /** Optional context object (e.g., Cloudflare Workers env via `context.cloudflare.env`). */
81
81
  context?: unknown;
82
82
  };
83
+ /**
84
+ * Arguments for {@link ActionDefinition.dispatch}, which lets a parent action
85
+ * invoke a sub-action **without** constructing a new `Request`.
86
+ *
87
+ * The parent passes its already-resolved {@link ActionContext} and the typed
88
+ * input directly. This avoids re-running `getContext()` (and thus the
89
+ * cookie-based session lookup) for the sub-action, which is both faster and
90
+ * eliminates the class of bugs described in issue #185 where a manually-built
91
+ * `Request` forgets to forward the `Cookie` header.
92
+ *
93
+ * @typeParam TInput - The expected input shape for the target action.
94
+ * @typeParam TUser - The shape of the authenticated user object.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * const parent = createAction((db, input, ctx) => ({
99
+ * permissions: [],
100
+ * async run() {
101
+ * // No Request needed — ctx is reused directly.
102
+ * const result = await child.dispatch({ ctx, input: { title: "Hello" } });
103
+ * return result;
104
+ * },
105
+ * }));
106
+ * ```
107
+ */
108
+ type DispatchArgs<TInput, TUser> = {
109
+ /** The parent action's already-resolved context, reused as-is. */
110
+ ctx: ActionContext<TUser>;
111
+ /** Typed input for the sub-action. */
112
+ input: TInput;
113
+ };
83
114
  /**
84
115
  * Configuration for the {@link createActions} factory.
85
116
  *
@@ -230,6 +261,30 @@ type ClientDescriptor = {
230
261
  type ActionDefinition<TInput, TResult, TUser> = {
231
262
  /** React Router action handler. Parses input, resolves context, and runs the operation. */
232
263
  action: (args: RequestArgs) => Promise<TResult>;
264
+ /**
265
+ * Dispatches the action using an already-resolved {@link ActionContext},
266
+ * bypassing `getContext()` entirely.
267
+ *
268
+ * Use this when a parent action handler needs to invoke a sub-action.
269
+ * Because the parent's `ctx` is reused directly, cookies, user session,
270
+ * and grants are inherited without constructing a new `Request` — which
271
+ * eliminates the cookie-forwarding bug described in issue #185.
272
+ *
273
+ * @param args - The {@link DispatchArgs} containing the parent's `ctx`
274
+ * and the typed input for this action.
275
+ * @returns The action's result, same as calling `.action()`.
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * const parent = createAction((db, input, ctx) => ({
280
+ * permissions: [],
281
+ * async run() {
282
+ * return child.dispatch({ ctx, input: { title: "Hello" } });
283
+ * },
284
+ * }));
285
+ * ```
286
+ */
287
+ dispatch: (args: DispatchArgs<TInput, TUser>) => Promise<TResult>;
233
288
  /**
234
289
  * Wraps a loader function to inject {@link ActionPermissionsMap} into its return value.
235
290
  *
@@ -285,4 +340,4 @@ type ComposedActions<TActions extends Record<string, ActionDefinition<any, any,
285
340
  actions: TActions;
286
341
  };
287
342
 
288
- export type { ActionPermissionStatus as A, ClientDescriptor as C, OperationsFn as O, RequestArgs as R, Serializable as S, ActionServices as a, ActionsConfig as b, ActionDefinition as c, ComposedActions as d, ActionContext as e, ActionPermissionsMap as f };
343
+ export type { ActionPermissionStatus as A, ClientDescriptor as C, DispatchArgs as D, OperationsFn as O, RequestArgs as R, Serializable as S, ActionServices as a, ActionsConfig as b, ActionDefinition as c, ComposedActions as d, ActionContext as e, ActionPermissionsMap as f };
package/llms.txt CHANGED
@@ -187,7 +187,37 @@ new body is `FormData` or `URLSearchParams`, so the platform can attach the
187
187
  correct multipart boundary. Pass an explicit `headers: { "content-type": ... }`
188
188
  to override.
189
189
 
190
- ### 5. Use on the client
190
+ ### 5. Dispatch sub-actions with `action.dispatch()` (preferred)
191
+
192
+ `action.dispatch({ ctx, input })` calls an action's operation directly,
193
+ bypassing Request construction entirely. No cookie forwarding, no FormData
194
+ serialisation, no `forwardRequest()` boilerplate -- just pass the context
195
+ and input you already have:
196
+
197
+ ```typescript
198
+ import { createRow } from "~/actions/rows";
199
+
200
+ export const importCsv = createAction(async (db, input, ctx) => ({
201
+ permissions: createRow.buildOperation(db, {} as never, ctx).permissions,
202
+ async run() {
203
+ for (const row of input.rows) {
204
+ await createRow.dispatch({
205
+ ctx: { db, user: ctx.user, grants: ctx.grants, services: {} },
206
+ input: { title: row.title },
207
+ });
208
+ }
209
+ return { ok: true };
210
+ },
211
+ }));
212
+ ```
213
+
214
+ `dispatch()` reuses the caller's `db`, `user`, and `grants` directly, so
215
+ permission checks still run on the sub-action's operation. Use `dispatch()`
216
+ for all server-to-server sub-action calls. `forwardRequest()` still works
217
+ but is only needed when you must go through the full HTTP action handler
218
+ (e.g., calling an action in a different Worker).
219
+
220
+ ### 6. Use on the client
191
221
  ```typescript
192
222
  import { useActions } from "@cfast/actions/client";
193
223
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfast/actions",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Multi-action routes and permission-aware action definitions for React Router",
5
5
  "keywords": [
6
6
  "cfast",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@cfast/db": ">=0.3.0 <0.5.0",
42
- "@cfast/permissions": ">=0.3.0 <0.5.0",
42
+ "@cfast/permissions": ">=0.3.0 <0.6.0",
43
43
  "react": "^19.0.0",
44
44
  "react-router": "^7.0.0"
45
45
  },
@@ -59,8 +59,8 @@
59
59
  "tsup": "^8",
60
60
  "typescript": "^5.7",
61
61
  "vitest": "^4.1.0",
62
- "@cfast/db": "0.4.0",
63
- "@cfast/permissions": "0.4.0"
62
+ "@cfast/db": "0.4.1",
63
+ "@cfast/permissions": "0.5.1"
64
64
  },
65
65
  "scripts": {
66
66
  "build": "tsup src/index.ts src/client.ts --format esm --dts",