@retronew/call-vue 0.3.0 → 0.4.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/README.md +136 -0
- package/dist/mutation-flow/index.d.mts +29 -0
- package/dist/mutation-flow/index.mjs +29 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -10,6 +10,26 @@ Call & await Vue components like async functions. A Vue 3 port of
|
|
|
10
10
|
(`createCallable`, `call`/`upsert`/`end`/`update`) built on native Vue
|
|
11
11
|
reactivity — no context providers, no global store to wire up.
|
|
12
12
|
|
|
13
|
+
[Documentation with live demos](https://call-vue.retronew.dev) ·
|
|
14
|
+
[Examples](https://call-vue.retronew.dev/examples) ·
|
|
15
|
+
[Concepts](https://call-vue.retronew.dev/concepts) ·
|
|
16
|
+
[Full API reference](https://call-vue.retronew.dev/api)
|
|
17
|
+
|
|
18
|
+
## Contents
|
|
19
|
+
|
|
20
|
+
- [Install](#install)
|
|
21
|
+
- [Quick start](#quick-start)
|
|
22
|
+
- [API](#api)
|
|
23
|
+
- [Exit transitions](#exit-transitions)
|
|
24
|
+
- [Root props and TypeScript](#root-props-and-typescript)
|
|
25
|
+
- [Mutation flow](#mutation-flow)
|
|
26
|
+
- [Async components](#async-components)
|
|
27
|
+
- [SSR](#ssr)
|
|
28
|
+
- [Stacking](#stacking)
|
|
29
|
+
- [Errors and troubleshooting](#errors-and-troubleshooting)
|
|
30
|
+
- [Capability matrix](#capability-matrix)
|
|
31
|
+
- [FAQ](#faq)
|
|
32
|
+
|
|
13
33
|
## Why
|
|
14
34
|
|
|
15
35
|
Confirmation dialogs, prompts, and toasts are usually one-off components you
|
|
@@ -30,6 +50,9 @@ No global state, no extra store — `<Confirm />` mounted once *is* the stack.
|
|
|
30
50
|
pnpm add @retronew/call-vue
|
|
31
51
|
```
|
|
32
52
|
|
|
53
|
+
Use `npm install @retronew/call-vue`, `yarn add @retronew/call-vue`, or
|
|
54
|
+
`bun add @retronew/call-vue` with another package manager.
|
|
55
|
+
|
|
33
56
|
## Quick start
|
|
34
57
|
|
|
35
58
|
1. Define the component. It receives your own props **plus** an injected
|
|
@@ -127,6 +150,19 @@ See the [Claude Code skill](skills/call-vue/SKILL.md) for the stacking model,
|
|
|
127
150
|
`unmountingDelay` exit-transition pattern, and the single-`<Root>` constraint
|
|
128
151
|
in depth.
|
|
129
152
|
|
|
153
|
+
### Public types
|
|
154
|
+
|
|
155
|
+
All public types are flat named exports:
|
|
156
|
+
|
|
157
|
+
| Type | Purpose |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `CallFunction<Props, Response>` | The typed `call()` method. |
|
|
160
|
+
| `UpsertFunction<Props, Response>` | The typed singleton `upsert()` method. |
|
|
161
|
+
| `CallContext<Props, Response, RootProps>` | The injected `call` prop. |
|
|
162
|
+
| `PropsWithCall<Props, Response, RootProps>` | Your props merged with `call`. |
|
|
163
|
+
| `UserComponent<Props, Response, RootProps>` | A component accepted by `createCallable`. |
|
|
164
|
+
| `Callable<Props, Response, RootProps>` | The Root component plus its imperative methods. |
|
|
165
|
+
|
|
130
166
|
## Exit transitions
|
|
131
167
|
|
|
132
168
|
Pass a second argument to `createCallable` to keep an ended call mounted
|
|
@@ -166,6 +202,39 @@ separate `.Root` alias. Its public type is Vue's general `Component` shape, so
|
|
|
166
202
|
it remains valid whether the internal Root is represented as an options object
|
|
167
203
|
or a functional component.
|
|
168
204
|
|
|
205
|
+
## Mutation flow
|
|
206
|
+
|
|
207
|
+
For the common “submit → await a side effect → close only on success” flow,
|
|
208
|
+
import the opt-in composable from its own subpath:
|
|
209
|
+
|
|
210
|
+
```vue
|
|
211
|
+
<script setup lang="ts">
|
|
212
|
+
import { toRef } from 'vue'
|
|
213
|
+
import type { PropsWithCall } from '@retronew/call-vue'
|
|
214
|
+
import { useMutationFlow, type MutationFn } from '@retronew/call-vue/mutation-flow'
|
|
215
|
+
|
|
216
|
+
type Props = { mutationFn: MutationFn<boolean> }
|
|
217
|
+
const props = defineProps<PropsWithCall<Props, boolean, {}>>()
|
|
218
|
+
const submit = useMutationFlow(props.call, toRef(props, 'mutationFn'))
|
|
219
|
+
</script>
|
|
220
|
+
|
|
221
|
+
<template>
|
|
222
|
+
<button :disabled="submit.pending" @click="submit()">Save</button>
|
|
223
|
+
<button :disabled="submit.pending" @click="props.call.end(false)">Cancel</button>
|
|
224
|
+
</template>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`MutationFn<Response, Payload>` receives only `{ end }` and decides when to
|
|
228
|
+
close the Call. If it returns or rejects without calling `end`, the Call stays
|
|
229
|
+
open and `pending` clears, so the user can retry. Errors are not swallowed.
|
|
230
|
+
|
|
231
|
+
When `mutationFn` is optional, `submit(payload).orEnd(value)` supplies a
|
|
232
|
+
per-button fallback response only when no handler was provided. Omitting the
|
|
233
|
+
chain intentionally leaves the Call open for another explicit close path.
|
|
234
|
+
|
|
235
|
+
Pass a `Ref` such as `toRef(props, 'mutationFn')` when a live Call can receive
|
|
236
|
+
an updated handler through `Callable.update()`.
|
|
237
|
+
|
|
169
238
|
## Async components
|
|
170
239
|
|
|
171
240
|
`defineAsyncComponent()` can be passed directly to `createCallable`. The loader
|
|
@@ -222,6 +291,73 @@ stack, or rendering all of them with a depth-based transform).
|
|
|
222
291
|
- Unmounting `<Confirm />` resets its stack — a fresh mount always starts
|
|
223
292
|
empty.
|
|
224
293
|
|
|
294
|
+
## Errors and troubleshooting
|
|
295
|
+
|
|
296
|
+
| Error or symptom | Cause and solution |
|
|
297
|
+
| --- | --- |
|
|
298
|
+
| `No <Root> found!` | Mount the returned Callable once and wait for client `onMounted` before calling it. During SSR, move the call to a client interaction. |
|
|
299
|
+
| `Multiple instances of <Root> found!` | The same Callable is mounted in more than one live location. Keep exactly one Root for that Callable. |
|
|
300
|
+
| The Promise never resolves | Every success, cancel, Escape, and backdrop path must explicitly run `call.end(response)` or an external `Callable.end(...)`. Hiding the UI is not enough. |
|
|
301
|
+
| Exit animation is cut off | Match `createCallable(component, unmountingDelay)` to the CSS leave duration and style against `call.ended`. |
|
|
302
|
+
| A targeted `void` end fails to type-check | Use `Toast.end(promise, undefined)`. `Toast.end()` is the broadcast form. |
|
|
303
|
+
| Root data appears missing | Read mounted Root props from `call.root`; normal call props remain top-level component props. |
|
|
304
|
+
|
|
305
|
+
The documentation site has the expanded
|
|
306
|
+
[troubleshooting guide](https://call-vue.retronew.dev/troubleshooting).
|
|
307
|
+
|
|
308
|
+
## Capability matrix
|
|
309
|
+
|
|
310
|
+
This package targets `react-call`'s framework-neutral core semantics, while
|
|
311
|
+
using Vue-native components and lifecycle primitives.
|
|
312
|
+
|
|
313
|
+
| Capability | `call-vue` | Notes |
|
|
314
|
+
| --- | --- | --- |
|
|
315
|
+
| `createCallable`, `call`, `end` | Supported | Promise and broadcast/targeted semantics match the upstream core. |
|
|
316
|
+
| Concurrent Stack | Supported | Every normal call remains independently active. |
|
|
317
|
+
| `upsert`, `update` | Supported | Singleton Promise identity and targeted/broadcast updates are covered. |
|
|
318
|
+
| Root props | Supported | Available reactively through `call.root`. |
|
|
319
|
+
| Exit lifecycle | Supported | `call.ended` plus `unmountingDelay`. |
|
|
320
|
+
| Vue async components | Supported | Use `defineAsyncComponent`; empty stacks stay lazy. |
|
|
321
|
+
| SSR-safe Root creation | Supported | Calling remains client-only. |
|
|
322
|
+
| `<Callable.Root />` alias | Not provided | The direct `<Callable />` Root is the only API; the legacy alias was removed rather than soft-deprecated. |
|
|
323
|
+
| Mutation-flow helper subpath | Supported | Import `useMutationFlow` and its types from `@retronew/call-vue/mutation-flow`. |
|
|
324
|
+
| Vite HMR transform | Not published | Normal Vue HMR applies, but open-call preservation is not promised yet. |
|
|
325
|
+
| Multi-preview host helper | Not published | Mount one Callable Root outside repeated Storybook/Histoire previews manually. |
|
|
326
|
+
|
|
327
|
+
Unsupported entries are deliberate capability boundaries, not hidden aliases.
|
|
328
|
+
Do not import `react-call`-specific subpaths from this package.
|
|
329
|
+
|
|
330
|
+
## FAQ
|
|
331
|
+
|
|
332
|
+
### What if more than one call is active?
|
|
333
|
+
|
|
334
|
+
The Root renders all Calls as a Stack in insertion order. Your component may
|
|
335
|
+
show all of them, position them by `call.index`, or visually prioritize the
|
|
336
|
+
latest using `call.stackSize`.
|
|
337
|
+
|
|
338
|
+
### Can I place more than one Root?
|
|
339
|
+
|
|
340
|
+
Not for the same Callable. You may mount one `Confirm`, one `Toast`, and one
|
|
341
|
+
`Picker` together because those are three independent Callable values.
|
|
342
|
+
|
|
343
|
+
### Does `upsert()` replace normal calls?
|
|
344
|
+
|
|
345
|
+
No. The singleton upsert instance coexists with normal `call()` instances.
|
|
346
|
+
Repeated upserts update only the singleton and return its original Promise.
|
|
347
|
+
|
|
348
|
+
### Is mutation flow just an async function?
|
|
349
|
+
|
|
350
|
+
The domain work is an async function. `useMutationFlow` additionally
|
|
351
|
+
standardizes pending state, duplicate-submit behavior, payload typing, retry
|
|
352
|
+
after failure, and the rule that only an explicit `call.end()` closes the Call.
|
|
353
|
+
Those semantics are why it ships as an optional subpath rather than in the
|
|
354
|
+
core entry.
|
|
355
|
+
|
|
356
|
+
### Can I use Teleport?
|
|
357
|
+
|
|
358
|
+
Yes. Teleport is presentation owned by your user component. Mount the Callable
|
|
359
|
+
Root once, then Teleport each rendered dialog or toast to the desired target.
|
|
360
|
+
|
|
225
361
|
## Claude Code skill
|
|
226
362
|
|
|
227
363
|
This package ships a [Claude Code skill](skills/call-vue/SKILL.md) covering
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Ref } from "vue";
|
|
2
|
+
//#region src/mutation-flow/index.d.ts
|
|
3
|
+
/** The narrow call view exposed to a mutation handler. */
|
|
4
|
+
type MutationCall<Response> = {
|
|
5
|
+
end: (response: Response) => void;
|
|
6
|
+
};
|
|
7
|
+
/** An async side-effect that decides when its Call should close. */
|
|
8
|
+
type MutationFn<Response, Payload = void> = (call: MutationCall<Response>, payload: Payload) => Promise<void>;
|
|
9
|
+
/** Runs a required mutation and exposes its in-flight state. */
|
|
10
|
+
type Trigger<Payload> = ((payload: Payload) => void) & {
|
|
11
|
+
pending: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** Adds a per-callsite fallback for an optional mutation. */
|
|
14
|
+
type ChainTrigger<Payload, Response> = ((payload: Payload) => {
|
|
15
|
+
orEnd: (value: Response) => void;
|
|
16
|
+
}) & {
|
|
17
|
+
pending: boolean;
|
|
18
|
+
};
|
|
19
|
+
type MutationSource<Response, Payload> = MutationFn<Response, Payload> | Readonly<Ref<MutationFn<Response, Payload> | undefined>> | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Coordinates an async submission with a Call's lifecycle.
|
|
22
|
+
*
|
|
23
|
+
* Pass a `Ref` when the handler can change while the Call is mounted, such as
|
|
24
|
+
* when `Callable.update()` replaces a `mutationFn` prop.
|
|
25
|
+
*/
|
|
26
|
+
declare function useMutationFlow<Response, Payload = void>(call: MutationCall<Response>, mutationFn: MutationFn<Response, Payload> | Readonly<Ref<MutationFn<Response, Payload>>>): Trigger<Payload>;
|
|
27
|
+
declare function useMutationFlow<Response, Payload = void>(call: MutationCall<Response>, mutationFn: MutationSource<Response, Payload>): ChainTrigger<Payload, Response>;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { ChainTrigger, MutationCall, MutationFn, Trigger, useMutationFlow };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isRef, ref } from "vue";
|
|
2
|
+
//#region src/mutation-flow/index.ts
|
|
3
|
+
const noopChain = { orEnd: () => {} };
|
|
4
|
+
function resolveMutation(source) {
|
|
5
|
+
return isRef(source) ? source.value : source;
|
|
6
|
+
}
|
|
7
|
+
function useMutationFlow(call, mutationSource) {
|
|
8
|
+
const pending = ref(false);
|
|
9
|
+
let inFlight = false;
|
|
10
|
+
const trigger = ((payload) => {
|
|
11
|
+
if (inFlight) return noopChain;
|
|
12
|
+
const mutationFn = resolveMutation(mutationSource);
|
|
13
|
+
if (!mutationFn) return { orEnd: (value) => call.end(value) };
|
|
14
|
+
inFlight = true;
|
|
15
|
+
pending.value = true;
|
|
16
|
+
mutationFn(call, payload).finally(() => {
|
|
17
|
+
inFlight = false;
|
|
18
|
+
pending.value = false;
|
|
19
|
+
});
|
|
20
|
+
return noopChain;
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(trigger, "pending", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
get: () => pending.value
|
|
25
|
+
});
|
|
26
|
+
return trigger;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { useMutationFlow };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retronew/call-vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Call & await Vue components like async functions — a Vue 3 port of react-call.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"async",
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"types": "./dist/index.d.mts",
|
|
33
33
|
"default": "./dist/index.mjs"
|
|
34
34
|
},
|
|
35
|
+
"./mutation-flow": {
|
|
36
|
+
"types": "./dist/mutation-flow/index.d.mts",
|
|
37
|
+
"default": "./dist/mutation-flow/index.mjs"
|
|
38
|
+
},
|
|
35
39
|
"./package.json": "./package.json"
|
|
36
40
|
},
|
|
37
41
|
"publishConfig": {
|