"jest-mock-warn": "^1.1.0",
"lint-staged": "^11.0.1",
"lodash.kebabcase": "^4.1.1",
+ "mande": "^1.0.0",
"pascalcase": "^1.0.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"rollup": "^2.53.2",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.30.0",
+ "swrv": "^1.0.0-beta.8",
"typescript": "~4.3.5",
"vite": "^2.4.1",
"vitepress": "^0.15.6",
"vue": "^3.2.0-beta.1",
+ "vue-promised": "^2.1.0",
"vue-router": "^4.0.10",
"yorkie": "^2.0.0"
},
</nav>
</header>
- <router-view />
+ <section>
+ <router-view />
+ </section>
+
+ <footer>
+ <p>
+ ©2021 Eduardo San Martin Morote
+ <br />
+ <a :href="sourceCodeLink">Source Code</a>
+ </p>
+ </footer>
</template>
<script lang="ts" setup>
-import { useRouter } from 'vue-router'
+import { computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
+const route = useRoute()
const pages = router
.getRoutes()
.filter((route) => !route.meta.hide)
.map((route) => ({ name: route.name }))
+
+const sourceCodeLink = computed(() => {
+ if (route.name) {
+ return `https://github.com/posva/pinia/blob/v2/playground/src/views/${route.name}.vue`
+ } else {
+ return `https://github.com/posva/pinia/blob/v2/playground/src/`
+ }
+})
</script>
<style>
id: 'counter',
state: () => ({
- n: 0,
+ n: 2,
incrementedTimes: 0,
decrementedTimes: 0,
numbers: [] as number[],
--- /dev/null
+import { ref, unref } from 'vue'
+import { acceptHMRUpdate, defineStore } from '../../../src'
+import { getRandomJoke, Joke } from '../views/api/jokes'
+
+export const useJokes = defineStore('jokes', {
+ state: () => ({
+ current: null as null | Joke,
+ jokes: [] as Joke[],
+ }),
+ actions: {
+ async fetchJoke() {
+ if (
+ this.current &&
+ // if the request below fails, avoid adding it twice
+ !this.jokes.includes(this.current)
+ ) {
+ this.jokes.push(this.current)
+ }
+
+ this.current = await getRandomJoke()
+ },
+ },
+})
+
+export const useJokesSetup = defineStore('jokes-setup', () => {
+ const current = ref<null | Joke>(null)
+ const history = ref<Joke[]>([])
+
+ async function fetchJoke() {
+ const cur = unref(current.value)
+ if (
+ cur &&
+ // if the request below fails, avoid adding it twice
+ !history.value.find((joke) => joke.id === cur.id)
+ ) {
+ history.value.push(cur)
+ }
+
+ current.value = await getRandomJoke()
+ return current.value
+ }
+
+ return { current, history, fetchJoke }
+})
+
+if (import.meta.hot) {
+ import.meta.hot.accept(acceptHMRUpdate(useJokes, import.meta.hot))
+ import.meta.hot.accept(acceptHMRUpdate(useJokesSetup, import.meta.hot))
+}
--- /dev/null
+import { acceptHMRUpdate, defineStore } from '../../../src'
+import { getRandomJoke, Joke } from '../views/api/jokes'
+import { usePromise } from 'vue-promised'
+import { ref, watch } from 'vue'
+
+export const useJokes = defineStore({
+ id: 'jokes-vue-promised',
+
+ state: () => {
+ const promise = ref(getRandomJoke())
+
+ watch(promise, () => {
+ console.log('promise changed')
+ })
+
+ const promised = usePromise(promise)
+
+ return {
+ promise,
+ ...promised,
+ history: [] as Joke[],
+ }
+ },
+
+ actions: {
+ waitForJoke() {
+ return this.promise
+ },
+
+ fetchJoke() {
+ if (
+ this.data &&
+ // if the request below fails, avoid adding it twice
+ !this.history.includes(this.data)
+ ) {
+ this.history.push(this.data)
+ }
+
+ console.log('fetching')
+ // this.$state.promise = getRandomJoke()
+ // Will fail because we initially had a ref
+ this.promise = getRandomJoke()
+
+ return this.$state.promise
+ },
+ },
+})
+
+export const useSetupJokes = defineStore('jokes-setup-vue-promised', () => {
+ const history = ref<Joke[]>([])
+ const promise = ref(getRandomJoke())
+ watch(promise, () => {
+ console.log('promise changed')
+ })
+
+ const promised = usePromise(promise)
+
+ function fetchJoke() {
+ if (
+ promised.data.value &&
+ // if the request below fails, avoid adding it twice
+ !history.value.includes(promised.data.value)
+ ) {
+ history.value.push(promised.data.value)
+ }
+
+ console.log('fetching')
+ // this.$state.promise = getRandomJoke()
+ // Will fail because we initially had a ref
+ promise.value = getRandomJoke()
+
+ promise.value.then((joke) => {
+ console.log('got', joke)
+ })
+
+ return promise.value
+ }
+
+ return {
+ promise,
+ ...promised,
+ history,
+ fetchJoke,
+ }
+})
+
+if (import.meta.hot) {
+ // import.meta.hot.accept(acceptHMRUpdate(useJokes, import.meta.hot))
+ // import.meta.hot.accept(acceptHMRUpdate(useSetupJokes, import.meta.hot))
+}
--- /dev/null
+<template>
+ <h3>
+ Pinia + <a href="https://github.com/posva/vue-promised">Vue Promised</a>
+ </h3>
+
+ <main>
+ <section>
+ <button
+ :disabled="state !== 'ready'"
+ @click="fetchRandomJoke"
+ style="margin-bottom: 4px"
+ >
+ {{ buttonText }}
+ </button>
+
+ <div style="min-height: 9rem" v-if="jokes.current">
+
+ <blockquote :key="jokes.current.id">
+ <i>{{ jokes.current.setup }}</i>
+ <br />
+ <br />
+ <p class="appear" @animationend="state = 'ready'">
+ {{ jokes.current.punchline }}
+ </p>
+ </blockquote>
+ </div>
+ </section>
+ </main>
+
+ <pre>{{ jokes.$state }}</pre>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue'
+import { useJokes, useJokesSetup } from '../stores/jokes'
+
+// const jokes = useJokes()
+const jokes = useJokesSetup()
+
+const texts = {
+ loading: 'Fetching the joke...',
+ waiting: 'Wait for it...',
+ ready: 'Another one?',
+}
+
+const state = ref<'waiting' | 'loading' | 'ready'>('waiting')
+
+const buttonText = computed(() => texts[state.value])
+
+function fetchRandomJoke() {
+ state.value = 'loading'
+
+ jokes.fetchJoke().finally(() => {
+ state.value = 'waiting'
+ console.log('done fetching', jokes.current)
+ })
+}
+
+onMounted(() => {
+ console.log('mounted')
+ // @ts-expect-error
+ window.jo = jokes
+ console.log('new pending')
+ fetchRandomJoke()
+})
+</script>
+
+<style>
+@keyframes appear {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.appear {
+ opacity: 0;
+ animation: appear 1s ease-in-out 3s;
+ animation-fill-mode: forwards;
+}
+</style>
--- /dev/null
+<template>
+ <h3>
+ Pinia + <a href="https://github.com/posva/vue-promised">Vue Promised</a>
+ </h3>
+
+ <main>
+ <section>
+ <button
+ :disabled="state !== 'ready'"
+ @click="fetchRandomJoke"
+ style="margin-bottom: 4px"
+ >
+ {{ buttonText }}
+ </button>
+
+ <div style="min-height: 9rem">
+ <template v-if="jokes.isPending && jokes.isDelayElapsed">
+ <div class="spinner"></div>
+ </template>
+
+ <template v-else-if="jokes.data">
+ <blockquote :key="jokes.data.id">
+ <i>{{ jokes.data.setup }}</i>
+ <br />
+ <br />
+ <p class="appear" @animationend="state = 'ready'">
+ {{ jokes.data.punchline }}
+ </p>
+ </blockquote>
+ </template>
+
+ <template v-else-if="jokes.error">
+ <p>Error: {{ jokes.error.message }}</p>
+ </template>
+ </div>
+ </section>
+ </main>
+
+ <pre>{{ jokes.$state }}</pre>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue'
+import { useJokes, useSetupJokes } from '../stores/jokesUsePromised'
+
+// const jokes = useJokes()
+const jokes = useSetupJokes()
+
+const texts = {
+ loading: 'Fetching the joke...',
+ waiting: 'Wait for it...',
+ ready: 'Another one?',
+}
+
+const state = ref<'waiting' | 'loading' | 'ready'>('waiting')
+
+const buttonText = computed(() => texts[state.value])
+
+function fetchRandomJoke() {
+ state.value = 'loading'
+
+ jokes.fetchJoke().finally(() => {
+ state.value = 'waiting'
+ console.log('done fetching', jokes.data)
+ })
+}
+
+onMounted(() => {
+ console.log('mounted')
+ // @ts-expect-error
+ window.jo = jokes
+ if (!jokes.isPending) {
+ console.log('new pending')
+ fetchRandomJoke()
+ }
+})
+</script>
+
+<style>
+@keyframes appear {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.appear {
+ opacity: 0;
+ animation: appear 1s ease-in-out 3s;
+ animation-fill-mode: forwards;
+}
+</style>
--- /dev/null
+import { mande } from 'mande'
+
+export const jokes = mande('https://official-joke-api.appspot.com', {
+ headers: {
+ 'Content-Type': null,
+ },
+})
+
+export interface Joke {
+ id: number
+ type: string
+ setup: string
+ punchline: string
+}
+
+export function getRandomJoke() {
+ return jokes.get<Joke>('/jokes/random')
+}
--- /dev/null
+import { mande } from 'mande'
+
+/**
+ * Go to https://api.nasa.gov/ and generate a key. Put it in your `.env` file
+ * next to the `package.json` file:
+ *
+ * VITE_API_KEY_NASA=<your_key>
+ */
+const API_KEY = import.meta.env.VITE_API_KEY_NASA || 'DEMO_KEY'
+
+const nasaPlanetary = mande('https://api.nasa.gov/planetary', {
+ query: { api_key: API_KEY, thumbs: true },
+})
+
+export interface NASAPOD {
+ copyright: string
+ date: string
+ explanation: string
+ hdurl: string
+ media_type: 'image' | 'video'
+ title: string
+ url: string
+}
+
+export function getNASAPOD(date: Date | string = new Date()) {
+ if (typeof date !== 'string') {
+ date = date.toISOString().slice(0, 10)
+ }
+
+ return nasaPlanetary.get<NASAPOD>('/apod', { query: { date } })
+}
--- /dev/null
+<template>
+ <h1></h1>
+</template>
hotState.value[key] = toRef(setupStore as any, key)
// createOptionStore already did this
} else if (!buildState) {
- pinia.state.value[$id][key] = toRef(setupStore as any, key)
+ pinia.state.value[$id][key] = prop
// TODO: avoid if state exists for SSR
}
dependencies:
tmpl "1.0.x"
+mande@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/mande/-/mande-1.0.0.tgz#80f1faae62ebe9bebdd37093794d25de39ceafab"
+ integrity sha512-SKNhkHOvXXFOGCgOvqD9EUKDiNZFXvfACfED+Omw7dXTq/NmHJ83QhHte3wOIiQ7bwLh7F/uotJ2djqcN0JgpA==
+
map-cache@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
has-flag "^4.0.0"
supports-color "^7.0.0"
+swrv@^1.0.0-beta.8:
+ version "1.0.0-beta.8"
+ resolved "https://registry.yarnpkg.com/swrv/-/swrv-1.0.0-beta.8.tgz#745723f5ca8a7a7e290e911cf7da34cecaf356d3"
+ integrity sha512-MsjaMOvZODfM0cess/HhbSrNbAotYinv4vzipLckKYBo/QmrvjNUPGZSRSqByXy/9AjrMRFWo0YanaVPbqADPQ==
+
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
vite "^2.3.7"
vue "^3.1.1"
-vue-demi@*:
+vue-demi@*, vue-demi@latest:
version "0.11.2"
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.11.2.tgz#faa06da53887c493a695b997f4fcb4784a667990"
integrity sha512-J+X8Au6BhQdcej6LY4O986634hZLu55L0ewU2j8my7WIKlu8cK0dqmdUxqVHHMd/cMrKKZ9SywB/id6aLhwCtA==
+vue-promised@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/vue-promised/-/vue-promised-2.1.0.tgz#dd565da4c24f0c974ccafc56def2514d9411b73b"
+ integrity sha512-AEcIeOR0bzSr5E1wq9MOSiX9ERO5zxi6jCOU3269EZbc+z6KXGOHIzPpeU+/SnvhTCXqJx9Nj3ff/KiFtgDfHA==
+ dependencies:
+ vue-demi latest
+
vue-router@^4.0.10:
version "4.0.10"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.10.tgz#ec8fda032949b2a31d3273170f8f376e86eb52ac"