From 3dda5ddbefb619d8ae5cb3a97623687d3aeab213 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Mon, 4 Sep 2023 21:49:06 +0200 Subject: [PATCH 01/20] feat(axios): Create package --- packages/axios/.eslintrc | 10 ++ packages/axios/.gitignore | 12 +++ packages/axios/LICENSE | 21 ++++ packages/axios/README.md | 37 +++++++ packages/axios/build.config.ts | 12 +++ packages/axios/package.json | 68 ++++++++++++ packages/axios/src/api/Request.ts | 160 +++++++++++++++++++++++++++++ packages/axios/src/api/Response.ts | 139 +++++++++++++++++++++++++ packages/axios/src/index.ts | 4 + packages/axios/src/types/config.ts | 22 ++++ packages/axios/tsconfig.json | 19 ++++ packages/axios/vitest.config.ts | 9 ++ pnpm-lock.yaml | 94 +++++++++++------ 13 files changed, 575 insertions(+), 32 deletions(-) create mode 100644 packages/axios/.eslintrc create mode 100644 packages/axios/.gitignore create mode 100644 packages/axios/LICENSE create mode 100644 packages/axios/README.md create mode 100644 packages/axios/build.config.ts create mode 100644 packages/axios/package.json create mode 100644 packages/axios/src/api/Request.ts create mode 100644 packages/axios/src/api/Response.ts create mode 100644 packages/axios/src/index.ts create mode 100644 packages/axios/src/types/config.ts create mode 100644 packages/axios/tsconfig.json create mode 100644 packages/axios/vitest.config.ts diff --git a/packages/axios/.eslintrc b/packages/axios/.eslintrc new file mode 100644 index 000000000..be379f8ef --- /dev/null +++ b/packages/axios/.eslintrc @@ -0,0 +1,10 @@ +{ + "extends": [ + "@nuxtjs/eslint-config-typescript" + ], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "off" + ] + } +} diff --git a/packages/axios/.gitignore b/packages/axios/.gitignore new file mode 100644 index 000000000..562fb249e --- /dev/null +++ b/packages/axios/.gitignore @@ -0,0 +1,12 @@ +.cache +.DS_Store +.idea +*.log +*.tgz +coverage +dist +lib-cov +logs +node_modules +temp +.idea diff --git a/packages/axios/LICENSE b/packages/axios/LICENSE new file mode 100644 index 000000000..206cdb527 --- /dev/null +++ b/packages/axios/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022-present Gregor Becker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/axios/README.md b/packages/axios/README.md new file mode 100644 index 000000000..239d9d8fb --- /dev/null +++ b/packages/axios/README.md @@ -0,0 +1,37 @@ +[![Pinia ORM banner](./.github/assets/banner.png)](https://github.com/storm-tail/pinia-orm) + +# @pinia-orm/axios + +[![npm version][npm-version-src]][npm-version-href] +[![npm downloads][npm-downloads-src]][npm-downloads-href] +[![Github Actions CI][github-actions-ci-src]][github-actions-ci-href] +[![License][license-src]][license-href] + +> Intuitive, type safe and flexible ORM for Pinia based on [Vuex ORM Next](https://github.com/vuex-orm/vuex-orm-next) + +- [✨  Release Notes](https://pinia-orm.codedredd.de/changelog) +- [📖  Documentation](https://pinia-orm.codedredd.de) + +## Help me keep working on this project 💚 + +- [Become a Sponsor on GitHub](https://github.com/sponsors/codedredd) +- [One-time donation via PayPal](https://paypal.me/dredd1984) +- [👾  Playground](https://pinia-orm-play.codedredd.de) + + + + + + +## License + +[MIT](http://opensource.org/licenses/MIT) + +[npm-version-src]: https://img.shields.io/npm/v/@pinia-orm/nuxt/latest.svg +[npm-version-href]: https://npmjs.com/package/@pinia-orm/nuxt +[npm-downloads-src]: https://img.shields.io/npm/dm/@pinia-orm/nuxt.svg +[npm-downloads-href]: https://npmjs.com/package/@pinia-orm/nuxt +[github-actions-ci-src]: https://github.com/codedredd/pinia-orm/actions/workflows/build.yml/badge.svg +[github-actions-ci-href]: https://github.com/codedredd/pinia-orm/actions?query=workflow%3Abuild +[license-src]: https://img.shields.io/npm/l/@pinia-orm/nuxt.svg +[license-href]: https://npmjs.com/package/@pinia-orm/nuxt diff --git a/packages/axios/build.config.ts b/packages/axios/build.config.ts new file mode 100644 index 000000000..6c5595886 --- /dev/null +++ b/packages/axios/build.config.ts @@ -0,0 +1,12 @@ +import { defineBuildConfig } from 'unbuild' + +export default defineBuildConfig({ + entries: [ + 'src/index' + ], + declaration: true, + clean: true, + rollup: { + emitCJS: true + } +}) diff --git a/packages/axios/package.json b/packages/axios/package.json new file mode 100644 index 000000000..d893fa1d4 --- /dev/null +++ b/packages/axios/package.json @@ -0,0 +1,68 @@ +{ + "name": "@pinia-orm/axios", + "version": "1.6.7", + "description": "Axios plugin for pinia-orm", + "bugs": { + "url": "https://github.com/CodeDredd/pinia-orm/issues" + }, + "homepage": "https://github.com/CodeDredd/pinia-orm", + "repository": { + "url": "https://github.com/CodeDredd/pinia-orm.git", + "type": "git" + }, + "keywords": [ + "axios", + "pinia-orm", + "api" + ], + "files": [ + "dist/", + "index.d.ts", + "LICENSE", + "README.md" + ], + "type": "module", + "funding": "https://github.com/sponsors/codedredd", + "jsdelivr": "dist/index.mjs", + "unpkg": "dist/index.mjs", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "index.d.ts", + "exports": { + ".": { + "require": "./dist/index.cjs", + "import": "./dist/index.mjs" + }, + "./*": "./*" + }, + "sideEffects": false, + "scripts": { + "build": "unbuild", + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . -l @pinia-orm/axios -r 1", + "size": "size-limit", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --fix --ext .ts" + }, + "author": { + "name": "Gregor Becker", + "email": "gregor@codedredd.de" + }, + "license": "MIT", + "devDependencies": { + "@nuxtjs/eslint-config-typescript": "^12.1.0", + "@size-limit/preset-small-lib": "^8.2.6", + "axios": "^1.5.0", + "axios-mock-adapter": "^1.21.5", + "eslint": "^8.48.0", + "pina-orm": "workspace:../pinia-orm", + "size-limit": "^8.2.6", + "typescript": "^5.2.2", + "unbuild": "^2.0.0" + }, + "size-limit": [ + { + "path": "dist/index.mjs", + "limit": "2 kB" + } + ] +} diff --git a/packages/axios/src/api/Request.ts b/packages/axios/src/api/Request.ts new file mode 100644 index 000000000..f2096b6f1 --- /dev/null +++ b/packages/axios/src/api/Request.ts @@ -0,0 +1,160 @@ +import { Repository } from 'pinia-orm' +import { AxiosInstance, AxiosResponse } from 'axios' +import { Config } from '../types/config' +import { Response } from './Response' + +export class Request { + /** + * The repository class. + */ + repository: typeof Repository + + /** + * The default config. + */ + config: Config = { + save: true + } + + /** + * Create a new api instance. + */ + constructor (repository: typeof Repository) { + this.repository = repository + + this.registerActions() + } + + /** + * Index key for the user defined actions. + */ + [action: string]: any + + /** + * Get the axios client. + */ + get axios (): AxiosInstance { + if (!this.repository.axios) { + throw new Error( + '[Vuex ORM Axios] The axios instance is not registered. Please register the axios instance to the repository.' + ) + } + + return this.repository.axios + } + + /** + * Register actions from the repository config. + */ + private registerActions (): void { + const actions = this.repository.apiConfig.actions + + if (!actions) { + return + } + + for (const name in actions) { + const action = actions[name] + + typeof action === 'function' + ? this.registerFunctionAction(name, action) + : this.registerObjectAction(name, action) + } + } + + /** + * Register the given object action. + */ + private registerObjectAction (name: string, action: any): void { + this[name] = (config: Config) => { + return this.request({ ...action, ...config }) + } + } + + /** + * Register the given function action. + */ + private registerFunctionAction (name: string, action: any): void { + this[name] = action.bind(this) + } + + /** + * Perform a get request. + */ + get (url: string, config: Config = {}): Promise { + return this.request({ method: 'get', url, ...config }) + } + + /** + * Perform a post request. + */ + post (url: string, data: any = {}, config: Config = {}): Promise { + return this.request({ method: 'post', url, data, ...config }) + } + + /** + * Perform a put request. + */ + put (url: string, data: any = {}, config: Config = {}): Promise { + return this.request({ method: 'put', url, data, ...config }) + } + + /** + * Perform a patch request. + */ + patch (url: string, data: any = {}, config: Config = {}): Promise { + return this.request({ method: 'patch', url, data, ...config }) + } + + /** + * Perform a delete request. + */ + delete (url: string, config: Config = {}): Promise { + return this.request({ method: 'delete', url, ...config }) + } + + /** + * Perform an api request. + */ + async request (config: Config): Promise { + const requestConfig = this.createConfig(config) + + const axiosResponse = await this.axios.request(requestConfig) + + return this.createResponse(axiosResponse, requestConfig) + } + + /** + * Create a new config by merging the global config, the repository config, + * and the given config. + */ + private createConfig (config: Config): Config { + return { + ...this.config, + ...this.repository.globalApiConfig, + ...this.repository.apiConfig, + ...config + } + } + + /** + * Create a new response instance by applying a few initialization processes. + * For example, it saves response data if `save` option id set to `true`. + */ + private async createResponse ( + axiosResponse: AxiosResponse, + config: Config + ): Promise { + const response = new Response(this.repository, config, axiosResponse) + + if (config.delete !== undefined) { + await response.delete() + + return response + } + + config.save && (await response.save()) + + return response + } +} diff --git a/packages/axios/src/api/Response.ts b/packages/axios/src/api/Response.ts new file mode 100644 index 000000000..fe0f75cb2 --- /dev/null +++ b/packages/axios/src/api/Response.ts @@ -0,0 +1,139 @@ +import { AxiosResponse } from 'axios' +import { Repository, Element, Collection } from 'pinia-orm' +import { Config, PersistMethods, PersistOptions } from '../types/config' + +export class Response { + /** + * The repository that called the request. + */ + repository: typeof Repository + + /** + * The request configuration. + */ + config: Config + + /** + * The axios response instance. + */ + response: AxiosResponse + + /** + * Entities created by Vuex ORM. + */ + entities: Collection | null = null + + /** + * Whether if response data is saved to the store or not. + */ + isSaved: boolean = false + + /** + * Create a new response instance. + */ + constructor (repository: typeof Repository, config: Config, response: AxiosResponse) { + this.repository = repository + this.config = config + this.response = response + } + + /** + * Save response data to the store. + */ + async save (): Promise { + const data = this.getDataFromResponse() + + if (!this.validateData(data)) { + console.warn( + '[Vuex ORM Axios] The response data could not be saved to the store ' + + 'because it is not an object or an array. You might want to use ' + + '`dataTransformer` option to handle non-array/object response ' + + 'before saving it to the store.' + ) + + return + } + + let method: PersistMethods = this.config.persistBy || 'save' + + if (!this.validatePersistAction(method)) { + console.warn( + '[Vuex ORM Axios] The "persistBy" option configured is not a ' + + 'recognized value. Response data will be persisted by the ' + + 'default `insertOrUpdate` method.' + ) + + method = 'save' + } + + const options = this.getPersistOptions() + + this.entities = await this.repository[method as string]({ data, ...options }) + + this.isSaved = true + } + + /** + * Delete the entity record where the `delete` option is configured. + */ + async delete (): Promise { + if (this.config.delete === undefined) { + throw new Error( + '[Vuex ORM Axios] Could not delete records because the `delete` option is not set.' + ) + } + + await this.repository.query().delete(this.config.delete as any) + } + + /** + * Get the response data from the axios response object. If a `dataTransformer` + * option is configured, it will be applied to the response object. If the + * `dataKey` option is configured, it will return the data from the given + * property within the response body. + */ + getDataFromResponse (): Element | Element[] { + if (this.config.dataTransformer) { + return this.config.dataTransformer(this.response) + } + + if (this.config.dataKey) { + return this.response.data[this.config.dataKey] + } + + return this.response.data + } + + /** + * Get persist options if any set in config. + */ + protected getPersistOptions (): PersistOptions | undefined { + const persistOptions = this.config.persistOptions + + if (!persistOptions || typeof persistOptions !== 'object') { + return + } + + return Object.keys(persistOptions) + .filter(this.validatePersistAction) // Filter to avoid polluting the payload. + .reduce((carry, key) => { + carry[key] = persistOptions[key] + return carry + }, {}) + } + + /** + * Validate the given data to ensure the Vuex ORM persist methods accept it. + */ + protected validateData (data: any): data is Element | Element[] { + return data !== null && typeof data === 'object' + } + + /** + * Validate the given string as to ensure it correlates with the available + * Vuex ORM persist methods. + */ + protected validatePersistAction (action: string): action is PersistMethods { + return ['save', 'insert'].includes(action) + } +} diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts new file mode 100644 index 000000000..0b0898f20 --- /dev/null +++ b/packages/axios/src/index.ts @@ -0,0 +1,4 @@ +import { Repository } from 'pinia-orm' +function useAxiosApi (repository: typeof Repository) { + +} diff --git a/packages/axios/src/types/config.ts b/packages/axios/src/types/config.ts new file mode 100644 index 000000000..762dd1202 --- /dev/null +++ b/packages/axios/src/types/config.ts @@ -0,0 +1,22 @@ +import { Element, Model } from 'pinia-orm' +import { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios' + +export type PersistMethods = 'save' | 'insert' + +export type PersistOptions = { [P in PersistMethods]?: string[] } + +export interface Config extends AxiosRequestConfig { + dataKey?: string + dataTransformer?: (response: AxiosResponse) => Element | Element[] + save?: boolean + persistBy?: PersistMethods + persistOptions?: PersistOptions + delete?: string | number | ((model: Model) => boolean) + actions?: { + [name: string]: any + } +} + +export interface GlobalConfig extends Config { + axios?: AxiosInstance +} diff --git a/packages/axios/tsconfig.json b/packages/axios/tsconfig.json new file mode 100644 index 000000000..e35bde3dd --- /dev/null +++ b/packages/axios/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "esnext", + "lib": [ + "esnext", + "dom" + ], + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "strictNullChecks": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "types": ["node"] + }, + "include": ["src", "test"] +} diff --git a/packages/axios/vitest.config.ts b/packages/axios/vitest.config.ts new file mode 100644 index 000000000..9614e4b38 --- /dev/null +++ b/packages/axios/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + reporter: ['text', 'clover', 'json'] + } + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afb28ae69..41532b897 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,36 @@ importers: specifier: ^2.0.0 version: 2.0.0 + packages/axios: + devDependencies: + '@nuxtjs/eslint-config-typescript': + specifier: ^12.1.0 + version: 12.1.0(eslint@8.48.0)(typescript@5.2.2) + '@size-limit/preset-small-lib': + specifier: ^8.2.6 + version: 8.2.6(size-limit@8.2.6) + axios: + specifier: ^1.5.0 + version: 1.5.0 + axios-mock-adapter: + specifier: ^1.21.5 + version: 1.21.5(axios@1.5.0) + eslint: + specifier: ^8.48.0 + version: 8.48.0 + pina-orm: + specifier: workspace:../pinia-orm + version: link:../pinia-orm + size-limit: + specifier: ^8.2.6 + version: 8.2.6 + typescript: + specifier: ^5.2.2 + version: 5.2.2 + unbuild: + specifier: ^2.0.0 + version: 2.0.0(typescript@5.2.2) + packages/normalizr: devDependencies: '@nuxtjs/eslint-config-typescript': @@ -3749,6 +3779,16 @@ packages: resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} dev: true + /axios-mock-adapter@1.21.5(axios@1.5.0): + resolution: {integrity: sha512-5NI1V/VK+8+JeTF8niqOowuysA4b8mGzdlMN/QnTnoXbYh4HZSNiopsDclN2g/m85+G++IrEtUdZaQ3GnaMsSA==} + peerDependencies: + axios: '>= 0.17.0' + dependencies: + axios: 1.5.0 + fast-deep-equal: 3.1.3 + is-buffer: 2.0.5 + dev: true + /axios@0.27.2: resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} dependencies: @@ -3758,6 +3798,16 @@ packages: - debug dev: true + /axios@1.5.0: + resolution: {integrity: sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==} + dependencies: + follow-redirects: 1.15.1 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: true + /b4a@1.6.4: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} dev: true @@ -5394,11 +5444,11 @@ packages: debug: 4.3.4 enhanced-resolve: 5.15.0 eslint: 8.48.0 - eslint-module-utils: 2.7.4(@typescript-eslint/parser@6.5.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.48.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.5.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.48.0) eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.5.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.48.0) fast-glob: 3.3.1 get-tsconfig: 4.7.0 - is-core-module: 2.12.1 + is-core-module: 2.13.0 is-glob: 4.0.3 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -5407,35 +5457,6 @@ packages: - supports-color dev: true - /eslint-module-utils@2.7.4(@typescript-eslint/parser@6.5.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.48.0): - resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 6.5.0(eslint@8.48.0)(typescript@5.2.2) - debug: 3.2.7 - eslint: 8.48.0 - eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@6.5.0)(eslint-plugin-import@2.28.1)(eslint@8.48.0) - transitivePeerDependencies: - - supports-color - dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.5.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.48.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} @@ -6997,6 +7018,11 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + dev: true + /is-builtin-module@3.2.1: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} @@ -9673,6 +9699,10 @@ packages: resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} dev: true + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true + /prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -9952,7 +9982,7 @@ packages: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} hasBin: true dependencies: - is-core-module: 2.12.1 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true From 34859f46b1fb56bb9fa4c3344a9fd318583ee421 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Mon, 4 Sep 2023 21:53:00 +0200 Subject: [PATCH 02/20] feat(axios): Create package --- packages/axios/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index 0b0898f20..47df7106b 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -1,4 +1,5 @@ import { Repository } from 'pinia-orm' +import { Request } from './api/Request' function useAxiosApi (repository: typeof Repository) { - + return new Request(repository) } From ea75a93b404781d7f2b86c853e09767caa8495ef Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Wed, 6 Sep 2023 22:25:57 +0200 Subject: [PATCH 03/20] refactor(axios): update working axios plugin --- packages/axios/package.json | 18 +- packages/axios/src/api/Request.ts | 2 +- packages/axios/src/api/Response.ts | 4 +- packages/axios/src/index.ts | 8 +- packages/axios/src/types/config.ts | 2 + packages/axios/test/feature/Request.spec.ts | 237 ++++++++++++++++++ .../test/feature/Request_Actions.spec.ts | 85 +++++++ .../test/feature/Request_DataKey.spec.ts | 46 ++++ .../feature/Request_DataTransformer.spec.ts | 46 ++++ .../axios/test/feature/Request_Delete.spec.ts | 50 ++++ .../axios/test/feature/Request_Save.spec.ts | 44 ++++ .../test/feature/Response_Delete.spec.ts | 67 +++++ .../axios/test/feature/Response_Save.spec.ts | 78 ++++++ packages/axios/test/helpers.ts | 46 ++++ packages/axios/test/setup.ts | 22 ++ packages/axios/vitest.config.ts | 5 +- .../pinia-orm/src/repository/Repository.ts | 15 +- pnpm-lock.yaml | 38 ++- 18 files changed, 781 insertions(+), 32 deletions(-) create mode 100644 packages/axios/test/feature/Request.spec.ts create mode 100644 packages/axios/test/feature/Request_Actions.spec.ts create mode 100644 packages/axios/test/feature/Request_DataKey.spec.ts create mode 100644 packages/axios/test/feature/Request_DataTransformer.spec.ts create mode 100644 packages/axios/test/feature/Request_Delete.spec.ts create mode 100644 packages/axios/test/feature/Request_Save.spec.ts create mode 100644 packages/axios/test/feature/Response_Delete.spec.ts create mode 100644 packages/axios/test/feature/Response_Save.spec.ts create mode 100644 packages/axios/test/helpers.ts create mode 100644 packages/axios/test/setup.ts diff --git a/packages/axios/package.json b/packages/axios/package.json index d893fa1d4..f40123d01 100644 --- a/packages/axios/package.json +++ b/packages/axios/package.json @@ -41,23 +41,35 @@ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . -l @pinia-orm/axios -r 1", "size": "size-limit", "lint": "eslint . --ext .ts", - "lint:fix": "eslint . --fix --ext .ts" + "lint:fix": "eslint . --fix --ext .ts", + "test:ui": "vue-demi-switch 3 && vitest --ui --api 9527", + "test:watch": "vue-demi-switch 3 && vitest --watch", + "test:2": "vue-demi-switch 2 vue2 && vitest --run", + "test:3": "vue-demi-switch 3 && vitest --run", + "test": "pnpm run test:3" }, "author": { "name": "Gregor Becker", "email": "gregor@codedredd.de" }, "license": "MIT", + "peerDependencies": { + "pinia-orm": ">=1.6.7" + }, "devDependencies": { "@nuxtjs/eslint-config-typescript": "^12.1.0", "@size-limit/preset-small-lib": "^8.2.6", + "@vitest/coverage-v8": "^0.34.3", "axios": "^1.5.0", "axios-mock-adapter": "^1.21.5", "eslint": "^8.48.0", - "pina-orm": "workspace:../pinia-orm", + "pinia-orm": "workspace:*", + "pinia": "^2.1.6", "size-limit": "^8.2.6", "typescript": "^5.2.2", - "unbuild": "^2.0.0" + "unbuild": "^2.0.0", + "vitest": "^0.34.3", + "vue-demi": "^0.14.6" }, "size-limit": [ { diff --git a/packages/axios/src/api/Request.ts b/packages/axios/src/api/Request.ts index f2096b6f1..9e1350ed0 100644 --- a/packages/axios/src/api/Request.ts +++ b/packages/axios/src/api/Request.ts @@ -47,7 +47,7 @@ export class Request { * Register actions from the repository config. */ private registerActions (): void { - const actions = this.repository.apiConfig.actions + const actions = { ...this.repository.apiConfig.actions, ...this.repository.getModel().$config()?.api?.actions } if (!actions) { return diff --git a/packages/axios/src/api/Response.ts b/packages/axios/src/api/Response.ts index fe0f75cb2..07c90e054 100644 --- a/packages/axios/src/api/Response.ts +++ b/packages/axios/src/api/Response.ts @@ -68,7 +68,7 @@ export class Response { const options = this.getPersistOptions() - this.entities = await this.repository[method as string]({ data, ...options }) + this.entities = await this.repository[method as string](data) this.isSaved = true } @@ -83,7 +83,7 @@ export class Response { ) } - await this.repository.query().delete(this.config.delete as any) + await this.repository.query().destroy(this.config.delete as any) } /** diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index 47df7106b..11acd030e 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -1,5 +1,9 @@ import { Repository } from 'pinia-orm' import { Request } from './api/Request' -function useAxiosApi (repository: typeof Repository) { - return new Request(repository) + +export function useAxiosApi (repository: typeof Repository) { + return new Request(repository) } + +export * from './api/Response' +export * from './api/Request' diff --git a/packages/axios/src/types/config.ts b/packages/axios/src/types/config.ts index 762dd1202..1fa604423 100644 --- a/packages/axios/src/types/config.ts +++ b/packages/axios/src/types/config.ts @@ -7,6 +7,8 @@ export type PersistOptions = { [P in PersistMethods]?: string[] } export interface Config extends AxiosRequestConfig { dataKey?: string + url?: string + method?: string dataTransformer?: (response: AxiosResponse) => Element | Element[] save?: boolean persistBy?: PersistMethods diff --git a/packages/axios/test/feature/Request.spec.ts b/packages/axios/test/feature/Request.spec.ts new file mode 100644 index 000000000..68e836ec7 --- /dev/null +++ b/packages/axios/test/feature/Request.spec.ts @@ -0,0 +1,237 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { useApiRepo, assertState } from '../helpers' + +describe('Feature - Request', () => { + let mock: MockAdapter + + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('`get` can perform a get request', async () => { + mock.onGet('/api/users').reply(200, [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ]) + + const userStore = useApiRepo(User) + + await userStore.api().get('/api/users') + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`get` can perform a get request with additional config', async () => { + mock.onGet('/api/users').reply(200, { + data: [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ] + }) + + const userStore = useApiRepo(User) + + await userStore.api().get('/api/users', { dataKey: 'data' }) + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`post` can perform a post request', async () => { + mock.onPost('/api/users').reply(200, [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ]) + + const userStore = useApiRepo(User) + + await userStore.api().post('/api/users') + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`post` can perform a post request with additional config', async () => { + mock.onPost('/api/users').reply(200, { + data: [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ] + }) + + const userStore = useApiRepo(User) + + await userStore.api().post('/api/users', {}, { dataKey: 'data' }) + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`put` can perform a put request', async () => { + mock.onPut('/api/users').reply(200, [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ]) + + const userStore = useApiRepo(User) + + await userStore.api().put('/api/users') + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`put` can perform a put request with additional config', async () => { + mock.onPut('/api/users').reply(200, { + data: [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ] + }) + + const userStore = useApiRepo(User) + + await userStore.api().put('/api/users', {}, { dataKey: 'data' }) + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`patch` can perform a patch request', async () => { + mock.onPatch('/api/users').reply(200, [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ]) + + const userStore = useApiRepo(User) + + await userStore.api().patch('/api/users') + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`patch` can perform a patch request with additional config', async () => { + mock.onPatch('/api/users').reply(200, { + data: [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ] + }) + + const userStore = useApiRepo(User) + + await userStore.api().patch('/api/users', {}, { dataKey: 'data' }) + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`delete` can perform a delete request', async () => { + mock.onDelete('/api/users').reply(200, [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ]) + + const userStore = useApiRepo(User) + + await userStore.api().delete('/api/users') + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('`delete` can perform a delete request with additional config', async () => { + mock.onDelete('/api/users').reply(200, { + data: [ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Doe' } + ] + }) + + const userStore = useApiRepo(User) + + await userStore.api().delete('/api/users', { dataKey: 'data' }) + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' }, + 2: { id: 2, name: 'Jane Doe' } + } + }) + }) + + it('throws error if `axios` is not set', async () => { + mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) + + const userStore = useApiRepo(User) + + const response = await userStore.api().get('/api/users') + + try { + await response.delete() + } catch (e) { + expect(e.message).toBe( + '[Vuex ORM Axios] Could not delete records because the `delete` option is not set.' + ) + + return + } + + throw new Error('Error was not thrown') + }) +}) diff --git a/packages/axios/test/feature/Request_Actions.spec.ts b/packages/axios/test/feature/Request_Actions.spec.ts new file mode 100644 index 000000000..4459347c4 --- /dev/null +++ b/packages/axios/test/feature/Request_Actions.spec.ts @@ -0,0 +1,85 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { useApiRepo, assertState } from '../helpers' +import { Request, Response } from '../../src' + +describe('Feature - Request - Actions', () => { + let mock: MockAdapter + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('can define a custom action', async () => { + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + + static config = { + api: { + actions: { + fetch: { method: 'get', url: '/users' } + } + } + } + } + + mock.onGet('/users').reply(200, { id: 1, name: 'John' }) + + const userStore = useApiRepo(User) + + await userStore.api().fetch() + + assertState({ + users: { + 1: { id: 1, name: 'John' } + } + }) + }) + + it('can define a custom action as a function', async () => { + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + + static config = { + api: { + actions: { + fetch (this: Request, url: string): Promise { + return this.get(url) + } + } + } + } + } + + mock.onGet('/users').reply(200, { id: 1, name: 'John' }) + + const userStore = useApiRepo(User) + + await userStore.api().fetch('/users') + + assertState({ + users: { + 1: { id: 1, name: 'John' } + } + }) + }) +}) diff --git a/packages/axios/test/feature/Request_DataKey.spec.ts b/packages/axios/test/feature/Request_DataKey.spec.ts new file mode 100644 index 000000000..b8a32564b --- /dev/null +++ b/packages/axios/test/feature/Request_DataKey.spec.ts @@ -0,0 +1,46 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { useApiRepo, assertState } from '../helpers' + +describe('Feature - Request - Data Key', () => { + let mock: MockAdapter + + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('can specify which resource key to extract data from', async () => { + mock.onGet('/users').reply(200, { + data: { id: 1, name: 'John Doe' } + }) + + const userStore = useApiRepo(User) + + await userStore.api().request({ + url: '/users', + dataKey: 'data' + }) + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' } + } + }) + }) +}) diff --git a/packages/axios/test/feature/Request_DataTransformer.spec.ts b/packages/axios/test/feature/Request_DataTransformer.spec.ts new file mode 100644 index 000000000..8b4b944a5 --- /dev/null +++ b/packages/axios/test/feature/Request_DataTransformer.spec.ts @@ -0,0 +1,46 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { useApiRepo, assertState } from '../helpers' + +describe('Feature - Request - Data Transformer', () => { + let mock: MockAdapter + + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('can specify a callback to transform the response', async () => { + mock.onGet('/users').reply(200, { + data: { id: 1, name: 'John Doe' } + }) + + const userStore = useApiRepo(User) + + await userStore.api().request({ + url: '/users', + dataTransformer: ({ data }) => data.data + }) + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' } + } + }) + }) +}) diff --git a/packages/axios/test/feature/Request_Delete.spec.ts b/packages/axios/test/feature/Request_Delete.spec.ts new file mode 100644 index 000000000..62fcfa812 --- /dev/null +++ b/packages/axios/test/feature/Request_Delete.spec.ts @@ -0,0 +1,50 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { useApiRepo, assertState } from '../helpers' + +describe('Feature - Request - Delete', () => { + let mock: MockAdapter + + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('can delete a record after the api call', async () => { + mock.onDelete('/users/1').reply(200, { ok: true }) + + const userStore = useApiRepo(User) + + userStore.save([ + { id: 1, name: 'John' }, + { id: 2, name: 'Jane' } + ]) + + await userStore.api().request({ + method: 'delete', + url: '/users/1', + delete: 1 + }) + + assertState({ + users: { + 2: { id: 2, name: 'Jane' } + } + }) + }) +}) diff --git a/packages/axios/test/feature/Request_Save.spec.ts b/packages/axios/test/feature/Request_Save.spec.ts new file mode 100644 index 000000000..e4d98b0b1 --- /dev/null +++ b/packages/axios/test/feature/Request_Save.spec.ts @@ -0,0 +1,44 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { useApiRepo, assertState } from '../helpers' + +describe('Feature - Request - Save', () => { + let mock: MockAdapter + + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('can prevent persisting response data to the store', async () => { + mock.onGet('/users').reply(200, { + data: { id: 1, name: 'John Doe' } + }) + + const userStore = useApiRepo(User) + + const result = await userStore.api().request({ + url: '/users', + save: false + }) + + expect(result.entities).toBe(null) + + assertState({}) + }) +}) diff --git a/packages/axios/test/feature/Response_Delete.spec.ts b/packages/axios/test/feature/Response_Delete.spec.ts new file mode 100644 index 000000000..1ed075794 --- /dev/null +++ b/packages/axios/test/feature/Response_Delete.spec.ts @@ -0,0 +1,67 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { useApiRepo, assertState } from '../helpers' + +describe('Feature - Response - Save', () => { + let mock: MockAdapter + + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('can save response data manually', async () => { + mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) + + const userStore = useApiRepo(User) + + const response = await userStore.api().get('/api/users') + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' } + } + }) + + response.config.delete = 1 + + await response.delete() + + assertState({ users: {} }) + }) + + it('throws error if `delete` option is not set', async () => { + mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) + + const userStore = useApiRepo(User) + + const response = await userStore.api().get('/api/users') + + try { + await response.delete() + } catch (e) { + expect(e.message).toBe( + '[Vuex ORM Axios] Could not delete records because the `delete` option is not set.' + ) + + return + } + + throw new Error('Error was not thrown') + }) +}) diff --git a/packages/axios/test/feature/Response_Save.spec.ts b/packages/axios/test/feature/Response_Save.spec.ts new file mode 100644 index 000000000..fc83bb3f5 --- /dev/null +++ b/packages/axios/test/feature/Response_Save.spec.ts @@ -0,0 +1,78 @@ +import axios from 'axios' +import MockAdapter from 'axios-mock-adapter' +import { Model } from 'pinia-orm' +import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest' +import { useApiRepo, assertState } from '../helpers' + +describe('Feature - Response - Save', () => { + let mock: MockAdapter + + class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + } + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + afterEach(() => { + mock.reset() + }) + + it('warns the user if the response data cannot be inserted', async () => { + const spy = vi.spyOn(console, 'warn') + + spy.mockImplementation(x => x) + + const userStore = useApiRepo(User) + + mock.onGet('/api/users').reply(200, null) + await userStore.api().get('/api/users') + + mock.onGet('/api/users').reply(200, 1) + await userStore.api().get('/api/users') + + expect(console.warn).toHaveBeenCalledTimes(2) + + spy.mockReset() + spy.mockRestore() + }) + + it('can save response data manually', async () => { + mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) + + const userStore = useApiRepo(User) + + const response = await userStore.api().get('/api/users', { save: false }) + + assertState({}) + + await response.save() + + assertState({ + users: { + 1: { id: 1, name: 'John Doe' } + } + }) + }) + + it('sets `isSaved` flag', async () => { + mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) + + const userStore = useApiRepo(User) + + const response = await userStore.api().get('/api/users', { save: false }) + + expect(response.isSaved).toBe(false) + + await response.save() + + expect(response.isSaved).toBe(true) + }) +}) diff --git a/packages/axios/test/helpers.ts b/packages/axios/test/helpers.ts new file mode 100644 index 000000000..6d54864e9 --- /dev/null +++ b/packages/axios/test/helpers.ts @@ -0,0 +1,46 @@ +import { createPinia, getActivePinia, setActivePinia } from 'pinia' +import { expect } from 'vitest' +import { Repository, Model, useRepo } from 'pinia-orm' +import type { Elements } from 'pinia-orm' +import axios from 'axios' +import { useAxiosApi } from '../src' + +interface Entities { + [name: string]: Elements +} + +export function createState (entities: Entities, additionalStoreProperties = {}): any { + const state = {} as any + + for (const entity in entities) { + if (!state[entity]) { state[entity] = { data: {}, ...additionalStoreProperties } } + + state[entity].data = entities[entity] + } + + return state +} + +export function fillState (entities: Entities): void { + // @ts-expect-error + getActivePinia().state.value = createState(entities) +} + +export function assertState (entities: Entities, additionalStoreProperties?: Record): void { + expect(getActivePinia()?.state.value).toEqual(createState(entities, additionalStoreProperties)) +} + +class ApiRepository extends Repository { + axios = axios + globalApiConfig = {} + apiConfig = {} + + api () { + return useAxiosApi(this) + } +} + +export function useApiRepo (model: M): ApiRepository { + ApiRepository.useModel = model + return useRepo(ApiRepository) +} diff --git a/packages/axios/test/setup.ts b/packages/axios/test/setup.ts new file mode 100644 index 000000000..780af6ca0 --- /dev/null +++ b/packages/axios/test/setup.ts @@ -0,0 +1,22 @@ +import { createPinia, setActivePinia } from 'pinia' +import { beforeAll, beforeEach } from 'vitest' +import { Vue2, createApp, install, isVue2 } from 'vue-demi' +import { Model, createORM, useRepo } from 'pinia-orm' + +beforeAll(() => { + if (isVue2) { + Vue2.config.productionTip = false + Vue2.config.devtools = false + install(Vue2) + } +}) + +beforeEach(() => { + const app = createApp({}) + const pinia = createPinia() + pinia.use(createORM()) + app.use(pinia) + setActivePinia(pinia) + Model.clearBootedModels() + useRepo(Model).hydratedDataCache.clear() +}) diff --git a/packages/axios/vitest.config.ts b/packages/axios/vitest.config.ts index 9614e4b38..91895939d 100644 --- a/packages/axios/vitest.config.ts +++ b/packages/axios/vitest.config.ts @@ -2,8 +2,11 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { + setupFiles: ['./test/setup.ts'], + externals: ['pinia-orm'], coverage: { - reporter: ['text', 'clover', 'json'] + enabled: true, + reporter: ['lcov', 'text', 'html'] } } }) diff --git a/packages/pinia-orm/src/repository/Repository.ts b/packages/pinia-orm/src/repository/Repository.ts index e44a031d8..687bdefd4 100644 --- a/packages/pinia-orm/src/repository/Repository.ts +++ b/packages/pinia-orm/src/repository/Repository.ts @@ -60,6 +60,11 @@ export class Repository { */ use?: typeof Model + /** + * The model object to be used for the custom repository. + */ + static useModel?: typeof Model + /** * Create a new Repository instance. */ @@ -88,7 +93,8 @@ export class Repository { // passed repository to the `store.$repo` method instead of a model. // In this case, we'll check if the user has set model to the `use` // property and instantiate that. - if (this.use) { + if (this.use || this.$self().useModel) { + this.use = this.use ?? this.$self().useModel as typeof Model this.model = this.use.newRawInstance() as M return this } @@ -98,6 +104,13 @@ export class Repository { return this } + /** + * Get the constructor for this model. + */ + $self (): typeof Repository { + return this.constructor as typeof Repository + } + /** * Get the model instance. If the model is not registered to the repository, * it will throw an error. It happens when users use a custom repository diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41532b897..43aafdd4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: '@size-limit/preset-small-lib': specifier: ^8.2.6 version: 8.2.6(size-limit@8.2.6) + '@vitest/coverage-v8': + specifier: ^0.34.3 + version: 0.34.3(vitest@0.34.3) axios: specifier: ^1.5.0 version: 1.5.0 @@ -95,8 +98,11 @@ importers: eslint: specifier: ^8.48.0 version: 8.48.0 - pina-orm: - specifier: workspace:../pinia-orm + pinia: + specifier: ^2.1.6 + version: 2.1.6(@vue/composition-api@1.7.2)(typescript@5.2.2)(vue@3.3.4) + pinia-orm: + specifier: workspace:* version: link:../pinia-orm size-limit: specifier: ^8.2.6 @@ -107,6 +113,12 @@ importers: unbuild: specifier: ^2.0.0 version: 2.0.0(typescript@5.2.2) + vitest: + specifier: ^0.34.3 + version: 0.34.3(@vitest/ui@0.34.3)(happy-dom@10.11.2) + vue-demi: + specifier: ^0.14.6 + version: 0.14.6(@vue/composition-api@1.7.2)(vue@3.3.4) packages/normalizr: devDependencies: @@ -2475,7 +2487,7 @@ packages: '@rollup/pluginutils': 5.0.4(rollup@3.28.1) commondir: 1.0.1 estree-walker: 2.0.2 - glob: 8.0.3 + glob: 8.1.0 is-reference: 1.2.1 magic-string: 0.27.0 rollup: 3.28.1 @@ -3258,7 +3270,7 @@ packages: '@vue/reactivity-transform': 3.3.4 '@vue/shared': 3.3.4 estree-walker: 2.0.2 - magic-string: 0.30.0 + magic-string: 0.30.3 postcss: 8.4.29 source-map-js: 1.0.2 dev: true @@ -6436,17 +6448,6 @@ packages: path-is-absolute: 1.0.1 dev: true - /glob@8.0.3: - resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==} - engines: {node: '>=12'} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - dev: true - /glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} @@ -7832,13 +7833,6 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /magic-string@0.30.0: - resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /magic-string@0.30.3: resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==} engines: {node: '>=12'} From b5f51bfe28b20c2203dc9641ca43f43dbbc8bdf9 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Fri, 8 Sep 2023 17:37:39 +0200 Subject: [PATCH 04/20] feat(axios): add composables & axios repository --- packages/axios/src/composables/useApiRepo.ts | 7 +++++++ packages/axios/src/composables/useAxiosApi.ts | 6 ++++++ packages/axios/src/index.ts | 10 +++------- packages/axios/src/repository/AxiosRepository.ts | 13 +++++++++++++ packages/axios/src/types/config.ts | 4 ++++ packages/axios/src/types/pinia-orm.ts | 7 +++++++ packages/axios/test/feature/Request.spec.ts | 3 ++- .../axios/test/feature/Request_Actions.spec.ts | 5 +++-- .../axios/test/feature/Request_DataKey.spec.ts | 3 ++- .../test/feature/Request_DataTransformer.spec.ts | 3 ++- .../axios/test/feature/Request_Delete.spec.ts | 3 ++- packages/axios/test/feature/Request_Save.spec.ts | 3 ++- .../axios/test/feature/Response_Delete.spec.ts | 3 ++- packages/axios/test/feature/Response_Save.spec.ts | 3 ++- packages/axios/test/helpers.ts | 15 --------------- packages/pinia-orm/src/store/Store.ts | 2 ++ 16 files changed, 59 insertions(+), 31 deletions(-) create mode 100644 packages/axios/src/composables/useApiRepo.ts create mode 100644 packages/axios/src/composables/useAxiosApi.ts create mode 100644 packages/axios/src/repository/AxiosRepository.ts create mode 100644 packages/axios/src/types/pinia-orm.ts diff --git a/packages/axios/src/composables/useApiRepo.ts b/packages/axios/src/composables/useApiRepo.ts new file mode 100644 index 000000000..2e7ac6292 --- /dev/null +++ b/packages/axios/src/composables/useApiRepo.ts @@ -0,0 +1,7 @@ +import { AxiosRepository } from '../repository/AxiosRepository' +import { useRepo, Model } from 'pinia-orm' + +export function useApiRepo (model: M): AxiosRepository { + AxiosRepository.useModel = model + return useRepo(AxiosRepository) +} diff --git a/packages/axios/src/composables/useAxiosApi.ts b/packages/axios/src/composables/useAxiosApi.ts new file mode 100644 index 000000000..4bb653c7e --- /dev/null +++ b/packages/axios/src/composables/useAxiosApi.ts @@ -0,0 +1,6 @@ +import { Repository } from 'pinia-orm' +import { Request } from '../api/Request' + +export function useAxiosApi (repository: typeof Repository) { + return new Request(repository) +} diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index 11acd030e..73dcf6016 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -1,9 +1,5 @@ -import { Repository } from 'pinia-orm' -import { Request } from './api/Request' - -export function useAxiosApi (repository: typeof Repository) { - return new Request(repository) -} - export * from './api/Response' export * from './api/Request' +export * from './repository/AxiosRepository' +export * from './composables/useAxiosApi' +export * from './composables/useApiRepo' diff --git a/packages/axios/src/repository/AxiosRepository.ts b/packages/axios/src/repository/AxiosRepository.ts new file mode 100644 index 000000000..8297cf16d --- /dev/null +++ b/packages/axios/src/repository/AxiosRepository.ts @@ -0,0 +1,13 @@ +import { Repository, Model, config } from 'pinia-orm' +import type { AxiosInstance } from 'axios' +import { useAxiosApi } from '../index' + +export class AxiosRepository extends Repository { + axios: AxiosInstance = config?.axios?.axios || null + globalApiConfig = config?.axios || {} + apiConfig = {} + + api () { + return useAxiosApi(this) + } +} diff --git a/packages/axios/src/types/config.ts b/packages/axios/src/types/config.ts index 1fa604423..d93153ba7 100644 --- a/packages/axios/src/types/config.ts +++ b/packages/axios/src/types/config.ts @@ -22,3 +22,7 @@ export interface Config extends AxiosRequestConfig { export interface GlobalConfig extends Config { axios?: AxiosInstance } + +export interface InstallConfig { + axios?: GlobalConfig +} diff --git a/packages/axios/src/types/pinia-orm.ts b/packages/axios/src/types/pinia-orm.ts new file mode 100644 index 000000000..e74d5a64e --- /dev/null +++ b/packages/axios/src/types/pinia-orm.ts @@ -0,0 +1,7 @@ +import type { InstallOptions as IOptions, FilledInstallOptions as FOptions } from 'pinia-orm' +import { InstallConfig } from './config' + +declare module 'pinia-orm' { + export type InstallOptions = IOptions & InstallConfig + export type FilledInstallOptions = FOptions & Required +} diff --git a/packages/axios/test/feature/Request.spec.ts b/packages/axios/test/feature/Request.spec.ts index 68e836ec7..4339fc9e7 100644 --- a/packages/axios/test/feature/Request.spec.ts +++ b/packages/axios/test/feature/Request.spec.ts @@ -2,7 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { useApiRepo, assertState } from '../helpers' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' describe('Feature - Request', () => { let mock: MockAdapter diff --git a/packages/axios/test/feature/Request_Actions.spec.ts b/packages/axios/test/feature/Request_Actions.spec.ts index 4459347c4..d4b8b5fb9 100644 --- a/packages/axios/test/feature/Request_Actions.spec.ts +++ b/packages/axios/test/feature/Request_Actions.spec.ts @@ -2,8 +2,9 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { useApiRepo, assertState } from '../helpers' -import { Request, Response } from '../../src' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' +import type { Request, Response } from '../../src' describe('Feature - Request - Actions', () => { let mock: MockAdapter diff --git a/packages/axios/test/feature/Request_DataKey.spec.ts b/packages/axios/test/feature/Request_DataKey.spec.ts index b8a32564b..2a83bf233 100644 --- a/packages/axios/test/feature/Request_DataKey.spec.ts +++ b/packages/axios/test/feature/Request_DataKey.spec.ts @@ -2,7 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { useApiRepo, assertState } from '../helpers' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' describe('Feature - Request - Data Key', () => { let mock: MockAdapter diff --git a/packages/axios/test/feature/Request_DataTransformer.spec.ts b/packages/axios/test/feature/Request_DataTransformer.spec.ts index 8b4b944a5..a9f574fff 100644 --- a/packages/axios/test/feature/Request_DataTransformer.spec.ts +++ b/packages/axios/test/feature/Request_DataTransformer.spec.ts @@ -2,7 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { useApiRepo, assertState } from '../helpers' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' describe('Feature - Request - Data Transformer', () => { let mock: MockAdapter diff --git a/packages/axios/test/feature/Request_Delete.spec.ts b/packages/axios/test/feature/Request_Delete.spec.ts index 62fcfa812..a8048671c 100644 --- a/packages/axios/test/feature/Request_Delete.spec.ts +++ b/packages/axios/test/feature/Request_Delete.spec.ts @@ -2,7 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { useApiRepo, assertState } from '../helpers' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' describe('Feature - Request - Delete', () => { let mock: MockAdapter diff --git a/packages/axios/test/feature/Request_Save.spec.ts b/packages/axios/test/feature/Request_Save.spec.ts index e4d98b0b1..7e9d4fa8c 100644 --- a/packages/axios/test/feature/Request_Save.spec.ts +++ b/packages/axios/test/feature/Request_Save.spec.ts @@ -2,7 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { useApiRepo, assertState } from '../helpers' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' describe('Feature - Request - Save', () => { let mock: MockAdapter diff --git a/packages/axios/test/feature/Response_Delete.spec.ts b/packages/axios/test/feature/Response_Delete.spec.ts index 1ed075794..72a51d3eb 100644 --- a/packages/axios/test/feature/Response_Delete.spec.ts +++ b/packages/axios/test/feature/Response_Delete.spec.ts @@ -2,7 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { useApiRepo, assertState } from '../helpers' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' describe('Feature - Response - Save', () => { let mock: MockAdapter diff --git a/packages/axios/test/feature/Response_Save.spec.ts b/packages/axios/test/feature/Response_Save.spec.ts index fc83bb3f5..2a8d6b225 100644 --- a/packages/axios/test/feature/Response_Save.spec.ts +++ b/packages/axios/test/feature/Response_Save.spec.ts @@ -2,7 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest' -import { useApiRepo, assertState } from '../helpers' +import { assertState } from '../helpers' +import { useApiRepo } from '../../src' describe('Feature - Response - Save', () => { let mock: MockAdapter diff --git a/packages/axios/test/helpers.ts b/packages/axios/test/helpers.ts index 6d54864e9..55b88870a 100644 --- a/packages/axios/test/helpers.ts +++ b/packages/axios/test/helpers.ts @@ -29,18 +29,3 @@ export function fillState (entities: Entities): void { export function assertState (entities: Entities, additionalStoreProperties?: Record): void { expect(getActivePinia()?.state.value).toEqual(createState(entities, additionalStoreProperties)) } - -class ApiRepository extends Repository { - axios = axios - globalApiConfig = {} - apiConfig = {} - - api () { - return useAxiosApi(this) - } -} - -export function useApiRepo (model: M): ApiRepository { - ApiRepository.useModel = model - return useRepo(ApiRepository) -} diff --git a/packages/pinia-orm/src/store/Store.ts b/packages/pinia-orm/src/store/Store.ts index c6fa91bbf..5a4c4b0a4 100644 --- a/packages/pinia-orm/src/store/Store.ts +++ b/packages/pinia-orm/src/store/Store.ts @@ -17,11 +17,13 @@ export interface CacheConfigOptions { export interface InstallOptions { model?: ModelConfigOptions cache?: CacheConfigOptions | boolean + [key: string]: any } export interface FilledInstallOptions { model: Required cache: Required + [key: string]: any } /** From 8f91be2813f194babaf06a068704f8ec55ae3b4b Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 06:33:04 +0200 Subject: [PATCH 05/20] refactor(axios): make build work & correct typings --- packages/axios/.eslintrc | 11 +++++- packages/axios/build.config.ts | 3 +- packages/axios/package.json | 5 ++- packages/axios/src/api/Request.ts | 10 ++--- packages/axios/src/api/Response.ts | 15 ++++---- packages/axios/src/composables/useApiRepo.ts | 4 +- packages/axios/src/composables/useAxiosApi.ts | 4 +- .../axios/src/repository/AxiosRepository.ts | 5 ++- packages/axios/src/types/config.ts | 2 +- packages/axios/src/types/pinia-orm.ts | 5 ++- .../test/feature/Request_Actions.spec.ts | 2 +- .../test/feature/Request_DataKey.spec.ts | 2 +- .../feature/Request_DataTransformer.spec.ts | 2 +- .../axios/test/feature/Request_Delete.spec.ts | 2 +- packages/axios/test/helpers.ts | 5 +-- packages/axios/tsconfig.json | 22 +++++------ packages/pinia-orm/src/composables/useRepo.ts | 2 +- .../pinia-orm/src/repository/Repository.ts | 4 +- packages/pinia-orm/src/store/Store.ts | 1 + pnpm-lock.yaml | 38 ++----------------- 20 files changed, 62 insertions(+), 82 deletions(-) diff --git a/packages/axios/.eslintrc b/packages/axios/.eslintrc index be379f8ef..0323daac5 100644 --- a/packages/axios/.eslintrc +++ b/packages/axios/.eslintrc @@ -3,8 +3,15 @@ "@nuxtjs/eslint-config-typescript" ], "rules": { - "@typescript-eslint/no-unused-vars": [ - "off" + "brace-style": "off", + "no-useless-constructor": "off", + "no-use-before-define": "off", + "no-self-compare": "off", + "func-call-spacing": "off", + "valid-typeof": "off", + "no-console": [ + "warn", + { "allow": ["clear", "info", "error", "dir", "trace", "time", "timeEnd", "warn"] } ] } } diff --git a/packages/axios/build.config.ts b/packages/axios/build.config.ts index 6c5595886..1a5c71d1d 100644 --- a/packages/axios/build.config.ts +++ b/packages/axios/build.config.ts @@ -8,5 +8,6 @@ export default defineBuildConfig({ clean: true, rollup: { emitCJS: true - } + }, + externals: ['axios', 'pinia-orm'] }) diff --git a/packages/axios/package.json b/packages/axios/package.json index f40123d01..df887fedd 100644 --- a/packages/axios/package.json +++ b/packages/axios/package.json @@ -27,7 +27,7 @@ "unpkg": "dist/index.mjs", "main": "./dist/index.cjs", "module": "./dist/index.mjs", - "types": "index.d.ts", + "types": "./dist/index.d.ts", "exports": { ".": { "require": "./dist/index.cjs", @@ -54,7 +54,8 @@ }, "license": "MIT", "peerDependencies": { - "pinia-orm": ">=1.6.7" + "pinia-orm": ">=1.6.7", + "axios": ">=1.5.0" }, "devDependencies": { "@nuxtjs/eslint-config-typescript": "^12.1.0", diff --git a/packages/axios/src/api/Request.ts b/packages/axios/src/api/Request.ts index 9e1350ed0..70551b040 100644 --- a/packages/axios/src/api/Request.ts +++ b/packages/axios/src/api/Request.ts @@ -1,13 +1,13 @@ -import { Repository } from 'pinia-orm' -import { AxiosInstance, AxiosResponse } from 'axios' +import type { AxiosInstance, AxiosResponse } from 'axios' import { Config } from '../types/config' +import { AxiosRepository } from '../repository/AxiosRepository' import { Response } from './Response' export class Request { /** * The repository class. */ - repository: typeof Repository + repository: AxiosRepository /** * The default config. @@ -19,7 +19,7 @@ export class Request { /** * Create a new api instance. */ - constructor (repository: typeof Repository) { + constructor (repository: AxiosRepository) { this.repository = repository this.registerActions() @@ -47,7 +47,7 @@ export class Request { * Register actions from the repository config. */ private registerActions (): void { - const actions = { ...this.repository.apiConfig.actions, ...this.repository.getModel().$config()?.api?.actions } + const actions = { ...this.repository.apiConfig.actions, ...this.repository.getModel().$config()?.axios?.actions } if (!actions) { return diff --git a/packages/axios/src/api/Response.ts b/packages/axios/src/api/Response.ts index 07c90e054..fb6117ab3 100644 --- a/packages/axios/src/api/Response.ts +++ b/packages/axios/src/api/Response.ts @@ -1,12 +1,13 @@ -import { AxiosResponse } from 'axios' -import { Repository, Element, Collection } from 'pinia-orm' +import type { AxiosResponse } from 'axios' +import { Element, Collection } from 'pinia-orm' import { Config, PersistMethods, PersistOptions } from '../types/config' +import { AxiosRepository } from '../repository/AxiosRepository' export class Response { /** * The repository that called the request. */ - repository: typeof Repository + repository: AxiosRepository /** * The request configuration. @@ -31,7 +32,7 @@ export class Response { /** * Create a new response instance. */ - constructor (repository: typeof Repository, config: Config, response: AxiosResponse) { + constructor (repository: AxiosRepository, config: Config, response: AxiosResponse) { this.repository = repository this.config = config this.response = response @@ -66,9 +67,9 @@ export class Response { method = 'save' } - const options = this.getPersistOptions() + const result = await this.repository[method](data) - this.entities = await this.repository[method as string](data) + this.entities = Array.isArray(result) ? result : [result] this.isSaved = true } @@ -119,7 +120,7 @@ export class Response { .reduce((carry, key) => { carry[key] = persistOptions[key] return carry - }, {}) + }, {} as PersistOptions) } /** diff --git a/packages/axios/src/composables/useApiRepo.ts b/packages/axios/src/composables/useApiRepo.ts index 2e7ac6292..6b6d4076d 100644 --- a/packages/axios/src/composables/useApiRepo.ts +++ b/packages/axios/src/composables/useApiRepo.ts @@ -1,7 +1,7 @@ -import { AxiosRepository } from '../repository/AxiosRepository' import { useRepo, Model } from 'pinia-orm' +import { AxiosRepository } from '../repository/AxiosRepository' -export function useApiRepo (model: M): AxiosRepository { +export function useApiRepo (model: M) { AxiosRepository.useModel = model return useRepo(AxiosRepository) } diff --git a/packages/axios/src/composables/useAxiosApi.ts b/packages/axios/src/composables/useAxiosApi.ts index 4bb653c7e..5ace17d3a 100644 --- a/packages/axios/src/composables/useAxiosApi.ts +++ b/packages/axios/src/composables/useAxiosApi.ts @@ -1,6 +1,6 @@ -import { Repository } from 'pinia-orm' import { Request } from '../api/Request' +import { AxiosRepository } from '../repository/AxiosRepository' -export function useAxiosApi (repository: typeof Repository) { +export function useAxiosApi (repository: AxiosRepository) { return new Request(repository) } diff --git a/packages/axios/src/repository/AxiosRepository.ts b/packages/axios/src/repository/AxiosRepository.ts index 8297cf16d..34500067c 100644 --- a/packages/axios/src/repository/AxiosRepository.ts +++ b/packages/axios/src/repository/AxiosRepository.ts @@ -1,11 +1,12 @@ import { Repository, Model, config } from 'pinia-orm' import type { AxiosInstance } from 'axios' import { useAxiosApi } from '../index' +import { Config } from '../types/config' -export class AxiosRepository extends Repository { +export class AxiosRepository extends Repository { axios: AxiosInstance = config?.axios?.axios || null globalApiConfig = config?.axios || {} - apiConfig = {} + apiConfig: Config = {} api () { return useAxiosApi(this) diff --git a/packages/axios/src/types/config.ts b/packages/axios/src/types/config.ts index d93153ba7..ad673e7c4 100644 --- a/packages/axios/src/types/config.ts +++ b/packages/axios/src/types/config.ts @@ -1,5 +1,5 @@ import { Element, Model } from 'pinia-orm' -import { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios' +import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios' export type PersistMethods = 'save' | 'insert' diff --git a/packages/axios/src/types/pinia-orm.ts b/packages/axios/src/types/pinia-orm.ts index e74d5a64e..b044d4428 100644 --- a/packages/axios/src/types/pinia-orm.ts +++ b/packages/axios/src/types/pinia-orm.ts @@ -1,7 +1,8 @@ -import type { InstallOptions as IOptions, FilledInstallOptions as FOptions } from 'pinia-orm' -import { InstallConfig } from './config' +import type { InstallOptions as IOptions, FilledInstallOptions as FOptions, ModelConfigOptions as MCOptions } from 'pinia-orm' +import { Config, InstallConfig } from './config' declare module 'pinia-orm' { export type InstallOptions = IOptions & InstallConfig export type FilledInstallOptions = FOptions & Required + export type ModelConfigOptions = MCOptions & { axios: Config } } diff --git a/packages/axios/test/feature/Request_Actions.spec.ts b/packages/axios/test/feature/Request_Actions.spec.ts index d4b8b5fb9..c767f6433 100644 --- a/packages/axios/test/feature/Request_Actions.spec.ts +++ b/packages/axios/test/feature/Request_Actions.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' -import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' import { useApiRepo } from '../../src' import type { Request, Response } from '../../src' diff --git a/packages/axios/test/feature/Request_DataKey.spec.ts b/packages/axios/test/feature/Request_DataKey.spec.ts index 2a83bf233..eac0936c4 100644 --- a/packages/axios/test/feature/Request_DataKey.spec.ts +++ b/packages/axios/test/feature/Request_DataKey.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' -import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' import { useApiRepo } from '../../src' diff --git a/packages/axios/test/feature/Request_DataTransformer.spec.ts b/packages/axios/test/feature/Request_DataTransformer.spec.ts index a9f574fff..39855bd01 100644 --- a/packages/axios/test/feature/Request_DataTransformer.spec.ts +++ b/packages/axios/test/feature/Request_DataTransformer.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' -import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' import { useApiRepo } from '../../src' diff --git a/packages/axios/test/feature/Request_Delete.spec.ts b/packages/axios/test/feature/Request_Delete.spec.ts index a8048671c..1665518e2 100644 --- a/packages/axios/test/feature/Request_Delete.spec.ts +++ b/packages/axios/test/feature/Request_Delete.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' -import { describe, expect, it, beforeEach, afterEach } from 'vitest' +import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' import { useApiRepo } from '../../src' diff --git a/packages/axios/test/helpers.ts b/packages/axios/test/helpers.ts index 55b88870a..509461211 100644 --- a/packages/axios/test/helpers.ts +++ b/packages/axios/test/helpers.ts @@ -1,9 +1,6 @@ -import { createPinia, getActivePinia, setActivePinia } from 'pinia' +import { getActivePinia } from 'pinia' import { expect } from 'vitest' -import { Repository, Model, useRepo } from 'pinia-orm' import type { Elements } from 'pinia-orm' -import axios from 'axios' -import { useAxiosApi } from '../src' interface Entities { [name: string]: Elements diff --git a/packages/axios/tsconfig.json b/packages/axios/tsconfig.json index e35bde3dd..0d9b23ea7 100644 --- a/packages/axios/tsconfig.json +++ b/packages/axios/tsconfig.json @@ -1,18 +1,18 @@ { "compilerOptions": { - "target": "es2018", - "module": "esnext", - "lib": [ - "esnext", - "dom" - ], - "moduleResolution": "node", - "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "preserve", + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Node", + "skipLibCheck": true, + "isolatedModules": true, + "useDefineForClassFields": true, "strict": true, - "strictNullChecks": true, + "allowJs": true, + "noEmit": true, "resolveJsonModule": true, - "skipLibCheck": true, - "skipDefaultLibCheck": true, + "allowSyntheticDefaultImports": true, "types": ["node"] }, "include": ["src", "test"] diff --git a/packages/pinia-orm/src/composables/useRepo.ts b/packages/pinia-orm/src/composables/useRepo.ts index 63187aefe..9fe3a86fc 100644 --- a/packages/pinia-orm/src/composables/useRepo.ts +++ b/packages/pinia-orm/src/composables/useRepo.ts @@ -5,7 +5,7 @@ import { Database } from '../database/Database' import type { Constructor } from '../types' export function useRepo( - repository: R, + repository: R | Constructor, pinia?: Pinia, ): R diff --git a/packages/pinia-orm/src/repository/Repository.ts b/packages/pinia-orm/src/repository/Repository.ts index c8bdc99aa..1bba56971 100644 --- a/packages/pinia-orm/src/repository/Repository.ts +++ b/packages/pinia-orm/src/repository/Repository.ts @@ -63,7 +63,7 @@ export class Repository { /** * The model object to be used for the custom repository. */ - static useModel?: typeof Model + static useModel?: Model /** * Create a new Repository instance. @@ -94,7 +94,7 @@ export class Repository { // In this case, we'll check if the user has set model to the `use` // property and instantiate that. if (this.use || this.$self().useModel) { - this.use = this.use ?? this.$self().useModel as typeof Model + this.use = (this.use ?? this.$self().useModel) as typeof Model this.model = this.use.newRawInstance() as M return this } diff --git a/packages/pinia-orm/src/store/Store.ts b/packages/pinia-orm/src/store/Store.ts index 19121b78b..a43a45f11 100644 --- a/packages/pinia-orm/src/store/Store.ts +++ b/packages/pinia-orm/src/store/Store.ts @@ -8,6 +8,7 @@ export interface ModelConfigOptions { hidden?: string[] namespace?: string visible?: string[] + [key: string]: any } export interface CacheConfigOptions { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 088f7bd40..5e6d71d89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2584,21 +2584,6 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/pluginutils@5.0.3(rollup@3.28.1): - resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.0 - estree-walker: 2.0.2 - picomatch: 2.3.1 - rollup: 3.28.1 - dev: true - /@rollup/pluginutils@5.0.4(rollup@3.28.1): resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==} engines: {node: '>=14.0.0'} @@ -4201,12 +4186,6 @@ packages: engines: {node: '>=8'} dev: true - /citty@0.1.2: - resolution: {integrity: sha512-Me9nf0/BEmMOnuQzMOVXgpzkMUNbd0Am8lTl/13p0aRGAoLGk5T5sdet/42CrIGmWdG67BgHUhcKK1my1ujUEg==} - dependencies: - consola: 3.2.3 - dev: true - /citty@0.1.3: resolution: {integrity: sha512-tb6zTEb2BDSrzFedqFYFUKUuKNaxVJWCm7o02K4kADGkBDyyiz7D40rDMpguczdZyAN3aetd5fhpB01HkreNyg==} dependencies: @@ -8161,15 +8140,6 @@ packages: typescript: 5.2.2 dev: true - /mlly@1.4.0: - resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} - dependencies: - acorn: 8.10.0 - pathe: 1.1.1 - pkg-types: 1.0.3 - ufo: 1.3.0 - dev: true - /mlly@1.4.1: resolution: {integrity: sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg==} dependencies: @@ -9242,7 +9212,7 @@ packages: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} dependencies: jsonc-parser: 3.2.0 - mlly: 1.4.1 + mlly: 1.4.2 pathe: 1.1.1 /pluralize@8.0.0: @@ -11244,9 +11214,9 @@ packages: '@rollup/plugin-json': 6.0.0(rollup@3.28.1) '@rollup/plugin-node-resolve': 15.2.1(rollup@3.28.1) '@rollup/plugin-replace': 5.0.2(rollup@3.28.1) - '@rollup/pluginutils': 5.0.3(rollup@3.28.1) + '@rollup/pluginutils': 5.0.4(rollup@3.28.1) chalk: 5.3.0 - citty: 0.1.2 + citty: 0.1.3 consola: 3.2.3 defu: 6.1.2 esbuild: 0.19.2 @@ -11255,7 +11225,7 @@ packages: jiti: 1.19.3 magic-string: 0.30.3 mkdist: 1.3.0(typescript@5.2.2) - mlly: 1.4.0 + mlly: 1.4.2 pathe: 1.1.1 pkg-types: 1.0.3 pretty-bytes: 6.1.1 From 07b5e793176429638b189f0abd889ff1ff0c1e0f Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 07:23:47 +0200 Subject: [PATCH 06/20] refactor(axios): update config --- packages/axios/src/repository/AxiosRepository.ts | 4 ++-- packages/axios/src/types/config.ts | 2 +- packages/axios/test/setup.ts | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/axios/src/repository/AxiosRepository.ts b/packages/axios/src/repository/AxiosRepository.ts index 34500067c..5d6850fac 100644 --- a/packages/axios/src/repository/AxiosRepository.ts +++ b/packages/axios/src/repository/AxiosRepository.ts @@ -4,8 +4,8 @@ import { useAxiosApi } from '../index' import { Config } from '../types/config' export class AxiosRepository extends Repository { - axios: AxiosInstance = config?.axios?.axios || null - globalApiConfig = config?.axios || {} + axios: AxiosInstance = config?.axiosApi?.axios || null + globalApiConfig = config?.axiosApi || {} apiConfig: Config = {} api () { diff --git a/packages/axios/src/types/config.ts b/packages/axios/src/types/config.ts index ad673e7c4..d60c6b36b 100644 --- a/packages/axios/src/types/config.ts +++ b/packages/axios/src/types/config.ts @@ -24,5 +24,5 @@ export interface GlobalConfig extends Config { } export interface InstallConfig { - axios?: GlobalConfig + axiosApi?: GlobalConfig } diff --git a/packages/axios/test/setup.ts b/packages/axios/test/setup.ts index 780af6ca0..6cdce8348 100644 --- a/packages/axios/test/setup.ts +++ b/packages/axios/test/setup.ts @@ -2,6 +2,7 @@ import { createPinia, setActivePinia } from 'pinia' import { beforeAll, beforeEach } from 'vitest' import { Vue2, createApp, install, isVue2 } from 'vue-demi' import { Model, createORM, useRepo } from 'pinia-orm' +import axios from 'axios' beforeAll(() => { if (isVue2) { @@ -14,7 +15,11 @@ beforeAll(() => { beforeEach(() => { const app = createApp({}) const pinia = createPinia() - pinia.use(createORM()) + pinia.use(createORM({ + axiosApi: { + axios: axios + } + })) app.use(pinia) setActivePinia(pinia) Model.clearBootedModels() From b7a06cdcba78cd1a5cb47f52038a3de2dd70d9d8 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 10:55:42 +0200 Subject: [PATCH 07/20] feat(pinia-orm): add plugins option --- packages/pinia-orm/src/composables/useRepo.ts | 5 +++ packages/pinia-orm/src/index.ts | 1 + .../pinia-orm/src/repository/Repository.ts | 20 ++++++++-- packages/pinia-orm/src/store/Config.ts | 2 +- packages/pinia-orm/src/store/Plugins.ts | 28 +++++++++++++ packages/pinia-orm/src/store/Store.ts | 11 ++++- .../eager_loads/eager_loads_all.spec.ts | 40 ++++++++++++++++++- 7 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 packages/pinia-orm/src/store/Plugins.ts diff --git a/packages/pinia-orm/src/composables/useRepo.ts b/packages/pinia-orm/src/composables/useRepo.ts index 9fe3a86fc..c1920df44 100644 --- a/packages/pinia-orm/src/composables/useRepo.ts +++ b/packages/pinia-orm/src/composables/useRepo.ts @@ -3,6 +3,7 @@ import type { Model } from '../model/Model' import { Repository } from '../repository/Repository' import { Database } from '../database/Database' import type { Constructor } from '../types' +import { registerPlugins } from '../store/Plugins' export function useRepo( repository: R | Constructor, @@ -20,10 +21,14 @@ export function useRepo ( ) { const database = new Database() + const { config } = registerPlugins() + const repository: Repository = ModelOrRepository._isRepository ? new ModelOrRepository(database, pinia).initialize() : new Repository(database, pinia).initialize(ModelOrRepository) + repository.setConfig(config) + try { const typeModels = Object.values(repository.getModel().$types()) if (typeModels.length > 0) { diff --git a/packages/pinia-orm/src/index.ts b/packages/pinia-orm/src/index.ts index 2e107653e..568d49df3 100644 --- a/packages/pinia-orm/src/index.ts +++ b/packages/pinia-orm/src/index.ts @@ -4,6 +4,7 @@ export * from './composables/useStoreActions' export * from './composables/useDataStore' export * from './composables/mapRepos' export * from './store/Config' +export * from './store/Plugins' export * from './store/Store' export * from './database/Database' export * from './schema/Schema' diff --git a/packages/pinia-orm/src/repository/Repository.ts b/packages/pinia-orm/src/repository/Repository.ts index 1bba56971..460767654 100644 --- a/packages/pinia-orm/src/repository/Repository.ts +++ b/packages/pinia-orm/src/repository/Repository.ts @@ -20,7 +20,8 @@ import { useDataStore } from '../composables/useDataStore' import { cache } from '../cache/SharedWeakCache' import { cache as hydratedDataCache } from '../cache/SharedHydratedDatakCache' import type { WeakCache } from '../cache/WeakCache' -import { config } from '../store/Config' +import { config as globalConfig } from '../store/Config' +import { FilledInstallOptions } from '@/store/Store' export class Repository { /** @@ -65,22 +66,35 @@ export class Repository { */ static useModel?: Model + /** + * Global config + */ + config: FilledInstallOptions + /** * Create a new Repository instance. */ constructor (database: Database, pinia?: Pinia) { + this.config = globalConfig this.database = database this.pinia = pinia this.hydratedDataCache = hydratedDataCache as Map } + /** + * Set the global config + */ + setConfig (config: FilledInstallOptions) { + this.config = config + } + /** * Initialize the repository by setting the model instance. */ initialize (model?: ModelConstructor): this { - if (config.cache && config.cache !== true) { + if (this.config.cache && this.config.cache !== true) { // eslint-disable-next-line new-cap - this.queryCache = (config.cache.shared ? cache : new config.cache.provider()) as WeakCache + this.queryCache = (this.config.cache.shared ? cache : new config.cache.provider()) as WeakCache } // If there's a model passed in, just use that and return immediately. diff --git a/packages/pinia-orm/src/store/Config.ts b/packages/pinia-orm/src/store/Config.ts index 0f6aab730..742b24159 100644 --- a/packages/pinia-orm/src/store/Config.ts +++ b/packages/pinia-orm/src/store/Config.ts @@ -14,4 +14,4 @@ export const CONFIG_DEFAULTS = { } } -export const config: FilledInstallOptions = { ...CONFIG_DEFAULTS } +export const config: FilledInstallOptions = CONFIG_DEFAULTS diff --git a/packages/pinia-orm/src/store/Plugins.ts b/packages/pinia-orm/src/store/Plugins.ts new file mode 100644 index 000000000..6f266a24e --- /dev/null +++ b/packages/pinia-orm/src/store/Plugins.ts @@ -0,0 +1,28 @@ +import { FilledInstallOptions, Model, Repository } from '../../src' +import { config as globalConfig } from './Config' + +export interface PiniaOrmPluginContext { + model: Model + repository: Repository + config: FilledInstallOptions +} + +export interface PiniaOrmPlugin { + (context: PiniaOrmPluginContext): void | Partial +} + +export const plugins: PiniaOrmPlugin[] = [] + +export function registerPlugins() { + let config = globalConfig + plugins.forEach(plugin => { + const pluginConfig = plugin({ config: config })?.config + if (pluginConfig) { + config = { ...config, ...pluginConfig } + } + }) + + return { + config + } +} diff --git a/packages/pinia-orm/src/store/Store.ts b/packages/pinia-orm/src/store/Store.ts index a43a45f11..4aa7de05f 100644 --- a/packages/pinia-orm/src/store/Store.ts +++ b/packages/pinia-orm/src/store/Store.ts @@ -2,6 +2,7 @@ import type { PiniaPlugin } from 'pinia' import type { WeakCache } from '../cache/WeakCache' import type { Model } from '../model/Model' import { CONFIG_DEFAULTS, config } from './Config' +import { PiniaOrmPlugin, plugins } from './Plugins' export interface ModelConfigOptions { withMeta?: boolean @@ -19,6 +20,7 @@ export interface CacheConfigOptions { export interface InstallOptions { model?: ModelConfigOptions cache?: CacheConfigOptions | boolean + plugins: Record [key: string]: any } @@ -32,7 +34,14 @@ export interface FilledInstallOptions { * Install Pinia ORM to the store. */ export function createORM (options?: InstallOptions): PiniaPlugin { + config.plugins = options?.plugins config.model = { ...CONFIG_DEFAULTS.model, ...options?.model } config.cache = options?.cache === false ? false : { ...CONFIG_DEFAULTS.cache, ...(options?.cache !== true && options?.cache) } - return () => {} + const orm = { + use(plugin: PiniaOrmPlugin) { + plugins.push(plugin) + return this + } + } + return () => orm } diff --git a/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts b/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts index 1d777354a..e7fe3e217 100644 --- a/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts +++ b/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' import { Model, useRepo } from '../../../../src' import { Attr, BelongsTo, HasMany, Str } from '../../../../src/decorators' -import { assertInstanceOf, assertModel } from '../../../helpers' +import { assertInstanceOf, assertModel, assertModels } from '../../../helpers' describe('feature/relations/eager_loads_all', () => { class User extends Model { @@ -57,4 +57,42 @@ describe('feature/relations/eager_loads_all', () => { comments: [{ id: 1, postId: 1, content: 'Content 01' }] }) }) + + it('eager loads without cached relations', () => { + const usersRepo = useRepo(User) + const postsRepo = useRepo(Post) + const commentsRepo = useRepo(Comment) + + usersRepo.save({ id: 1, name: 'John Doe' }) + + postsRepo.save({ id: 1, userId: 1, title: 'Title 01' }) + + commentsRepo.save({ id: 1, postId: 1, content: 'Content 01' }) + + const onlyPosts = postsRepo.all() + const posts = postsRepo.withAllRecursive().get() + const onlyPosts2 = postsRepo.all() + + console.log(posts, onlyPosts) + + assertModels(posts, [{ + id: 1, + userId: 1, + title: 'Title 01', + author: { id: 1, name: 'John Doe' }, + comments: [{ id: 1, postId: 1, content: 'Content 01' }] + }]) + + assertModels(onlyPosts, [{ + id: 1, + userId: 1, + title: 'Title 01' + }]) + + assertModels(onlyPosts2, [{ + id: 1, + userId: 1, + title: 'Title 01' + }]) + }) }) From 9de42a205e934925bda859e7c3720c2c79920335 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 11:46:58 +0200 Subject: [PATCH 08/20] feat(pinia-orm): finish add plugin feature --- packages/pinia-orm/src/composables/useRepo.ts | 6 +--- .../pinia-orm/src/repository/Repository.ts | 4 +-- packages/pinia-orm/src/store/Config.ts | 2 +- packages/pinia-orm/src/store/Plugins.ts | 18 ++++++------ packages/pinia-orm/src/store/Store.ts | 9 +++--- .../tests/feature/plugin/plugin.test.ts | 28 +++++++++++++++++++ packages/pinia-orm/tests/helpers.ts | 10 +++++-- 7 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 packages/pinia-orm/tests/feature/plugin/plugin.test.ts diff --git a/packages/pinia-orm/src/composables/useRepo.ts b/packages/pinia-orm/src/composables/useRepo.ts index c1920df44..23b2fa4a6 100644 --- a/packages/pinia-orm/src/composables/useRepo.ts +++ b/packages/pinia-orm/src/composables/useRepo.ts @@ -21,14 +21,10 @@ export function useRepo ( ) { const database = new Database() - const { config } = registerPlugins() - const repository: Repository = ModelOrRepository._isRepository ? new ModelOrRepository(database, pinia).initialize() : new Repository(database, pinia).initialize(ModelOrRepository) - repository.setConfig(config) - try { const typeModels = Object.values(repository.getModel().$types()) if (typeModels.length > 0) { @@ -38,5 +34,5 @@ export function useRepo ( } } catch (e) {} - return repository + return registerPlugins(repository) } diff --git a/packages/pinia-orm/src/repository/Repository.ts b/packages/pinia-orm/src/repository/Repository.ts index 460767654..b33da50b2 100644 --- a/packages/pinia-orm/src/repository/Repository.ts +++ b/packages/pinia-orm/src/repository/Repository.ts @@ -69,7 +69,7 @@ export class Repository { /** * Global config */ - config: FilledInstallOptions + config: FilledInstallOptions & { [key: string]: any } /** * Create a new Repository instance. @@ -94,7 +94,7 @@ export class Repository { initialize (model?: ModelConstructor): this { if (this.config.cache && this.config.cache !== true) { // eslint-disable-next-line new-cap - this.queryCache = (this.config.cache.shared ? cache : new config.cache.provider()) as WeakCache + this.queryCache = (this.config.cache.shared ? cache : new this.config.cache.provider()) as WeakCache } // If there's a model passed in, just use that and return immediately. diff --git a/packages/pinia-orm/src/store/Config.ts b/packages/pinia-orm/src/store/Config.ts index 742b24159..0f6aab730 100644 --- a/packages/pinia-orm/src/store/Config.ts +++ b/packages/pinia-orm/src/store/Config.ts @@ -14,4 +14,4 @@ export const CONFIG_DEFAULTS = { } } -export const config: FilledInstallOptions = CONFIG_DEFAULTS +export const config: FilledInstallOptions = { ...CONFIG_DEFAULTS } diff --git a/packages/pinia-orm/src/store/Plugins.ts b/packages/pinia-orm/src/store/Plugins.ts index 6f266a24e..6f14a812e 100644 --- a/packages/pinia-orm/src/store/Plugins.ts +++ b/packages/pinia-orm/src/store/Plugins.ts @@ -4,25 +4,23 @@ import { config as globalConfig } from './Config' export interface PiniaOrmPluginContext { model: Model repository: Repository - config: FilledInstallOptions + config: FilledInstallOptions & { [key: string]: any } } export interface PiniaOrmPlugin { - (context: PiniaOrmPluginContext): void | Partial + (context: PiniaOrmPluginContext): PiniaOrmPluginContext } export const plugins: PiniaOrmPlugin[] = [] -export function registerPlugins() { +export function registerPlugins(repository: Repository) { let config = globalConfig plugins.forEach(plugin => { - const pluginConfig = plugin({ config: config })?.config - if (pluginConfig) { - config = { ...config, ...pluginConfig } - } + const pluginConfig = plugin({ config, repository, model: repository.getModel() }) + config = { ...config, ...pluginConfig.config } }) - return { - config - } + repository.setConfig(config) + + return repository } diff --git a/packages/pinia-orm/src/store/Store.ts b/packages/pinia-orm/src/store/Store.ts index 4aa7de05f..3ec166e91 100644 --- a/packages/pinia-orm/src/store/Store.ts +++ b/packages/pinia-orm/src/store/Store.ts @@ -9,7 +9,6 @@ export interface ModelConfigOptions { hidden?: string[] namespace?: string visible?: string[] - [key: string]: any } export interface CacheConfigOptions { @@ -27,14 +26,16 @@ export interface InstallOptions { export interface FilledInstallOptions { model: Required cache: Required - [key: string]: any +} + +export interface CreatePiniaOrm { + use(plugin: PiniaOrmPlugin): this } /** * Install Pinia ORM to the store. */ -export function createORM (options?: InstallOptions): PiniaPlugin { - config.plugins = options?.plugins +export function createORM (options?: InstallOptions) { config.model = { ...CONFIG_DEFAULTS.model, ...options?.model } config.cache = options?.cache === false ? false : { ...CONFIG_DEFAULTS.cache, ...(options?.cache !== true && options?.cache) } const orm = { diff --git a/packages/pinia-orm/tests/feature/plugin/plugin.test.ts b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts new file mode 100644 index 000000000..1a7960e97 --- /dev/null +++ b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { Model, useRepo } from '../../../src' +import { Attr, Str } from '../../../src/decorators' +import { createPiniaORM } from '../../helpers' +import { PiniaOrmPlugin } from '../../../src' + +describe('feature/plugin/plugin', () => { + class User extends Model { + static entity = 'users' + + @Attr(0) declare id: number + @Str('') declare name: string + @Str('') declare username: string + } + + it('can add extra config to the configuration', () => { + const plugin: PiniaOrmPlugin = (context) => { + context.config.apiConfig = 'test' + return context + } + + createPiniaORM(undefined, [plugin]) + + const userRepo = useRepo(User) + + expect(userRepo.config.apiConfig).toBe('test') + }) +}) diff --git a/packages/pinia-orm/tests/helpers.ts b/packages/pinia-orm/tests/helpers.ts index a5065bf69..2a4f7759e 100644 --- a/packages/pinia-orm/tests/helpers.ts +++ b/packages/pinia-orm/tests/helpers.ts @@ -7,7 +7,7 @@ import type { Mock } from 'vitest' import { expect, vi } from 'vitest' import { createApp } from 'vue-demi' -import type { Collection, Elements, InstallOptions, Model } from '../src' +import type { Collection, Elements, InstallOptions, Model, PiniaOrmPlugin } from '../src' import * as Utils from '../src/support/Utils' import { createORM } from '../src' @@ -15,10 +15,14 @@ interface Entities { [name: string]: Elements } -export function createPiniaORM (options?: InstallOptions) { +export function createPiniaORM (options?: InstallOptions, plugins?: PiniaOrmPlugin[]) { const app = createApp({}) const pinia = createPinia() - pinia.use(createORM(options)) + const piniaOrm = createORM(options) + if (plugins) { + plugins.forEach(plugin => piniaOrm().use(plugin)) + } + pinia.use(piniaOrm) app.use(pinia) setActivePinia(pinia) } From 63e37b85a873600d70e376070c14c6388379f52b Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 12:13:36 +0200 Subject: [PATCH 09/20] test(axios): make tests work --- packages/axios/src/api/Request.ts | 2 +- packages/axios/src/index.ts | 1 + packages/axios/src/plugin.ts | 9 +++++++++ packages/axios/test/feature/Request_Actions.spec.ts | 4 ++-- packages/axios/test/setup.ts | 9 +++++---- 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 packages/axios/src/plugin.ts diff --git a/packages/axios/src/api/Request.ts b/packages/axios/src/api/Request.ts index 70551b040..ffef55e2a 100644 --- a/packages/axios/src/api/Request.ts +++ b/packages/axios/src/api/Request.ts @@ -47,7 +47,7 @@ export class Request { * Register actions from the repository config. */ private registerActions (): void { - const actions = { ...this.repository.apiConfig.actions, ...this.repository.getModel().$config()?.axios?.actions } + const actions = { ...this.repository.config.axiosApi?.actions, ...this.repository.getModel().$config()?.axiosApi?.actions } if (!actions) { return diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index 73dcf6016..01a55f774 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -3,3 +3,4 @@ export * from './api/Request' export * from './repository/AxiosRepository' export * from './composables/useAxiosApi' export * from './composables/useApiRepo' +export * from './plugin' diff --git a/packages/axios/src/plugin.ts b/packages/axios/src/plugin.ts new file mode 100644 index 000000000..c93872aaf --- /dev/null +++ b/packages/axios/src/plugin.ts @@ -0,0 +1,9 @@ +import { PiniaOrmPlugin } from 'pinia-orm' +import { GlobalConfig } from './types/config' + +export function piniaOrmPluginAxios (axiosConfig?: GlobalConfig): PiniaOrmPlugin { + return (context) => { + context.config.axiosApi = axiosConfig + return context + } +} diff --git a/packages/axios/test/feature/Request_Actions.spec.ts b/packages/axios/test/feature/Request_Actions.spec.ts index c767f6433..9dc9337ac 100644 --- a/packages/axios/test/feature/Request_Actions.spec.ts +++ b/packages/axios/test/feature/Request_Actions.spec.ts @@ -28,7 +28,7 @@ describe('Feature - Request - Actions', () => { } static config = { - api: { + axiosApi: { actions: { fetch: { method: 'get', url: '/users' } } @@ -61,7 +61,7 @@ describe('Feature - Request - Actions', () => { } static config = { - api: { + axiosApi: { actions: { fetch (this: Request, url: string): Promise { return this.get(url) diff --git a/packages/axios/test/setup.ts b/packages/axios/test/setup.ts index 6cdce8348..0dc6bbea1 100644 --- a/packages/axios/test/setup.ts +++ b/packages/axios/test/setup.ts @@ -3,6 +3,7 @@ import { beforeAll, beforeEach } from 'vitest' import { Vue2, createApp, install, isVue2 } from 'vue-demi' import { Model, createORM, useRepo } from 'pinia-orm' import axios from 'axios' +import { piniaOrmPluginAxios } from '../src' beforeAll(() => { if (isVue2) { @@ -15,11 +16,11 @@ beforeAll(() => { beforeEach(() => { const app = createApp({}) const pinia = createPinia() - pinia.use(createORM({ - axiosApi: { - axios: axios - } + const piniaOrm = createORM() + piniaOrm().use(piniaOrmPluginAxios({ + axios: axios })) + pinia.use(piniaOrm) app.use(pinia) setActivePinia(pinia) Model.clearBootedModels() From 8cd1cc95b9c8e6f762aba37acb154ec78ffaffb6 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 12:14:07 +0200 Subject: [PATCH 10/20] refactor(pinia-orm): cleanup types --- packages/pinia-orm/src/store/Config.ts | 2 +- packages/pinia-orm/src/store/Store.ts | 2 -- packages/pinia-orm/tests/unit/PiniaORM.spec.ts | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/pinia-orm/src/store/Config.ts b/packages/pinia-orm/src/store/Config.ts index 0f6aab730..d1bfe1bc7 100644 --- a/packages/pinia-orm/src/store/Config.ts +++ b/packages/pinia-orm/src/store/Config.ts @@ -14,4 +14,4 @@ export const CONFIG_DEFAULTS = { } } -export const config: FilledInstallOptions = { ...CONFIG_DEFAULTS } +export const config: FilledInstallOptions & { [key: string]: any } = { ...CONFIG_DEFAULTS } diff --git a/packages/pinia-orm/src/store/Store.ts b/packages/pinia-orm/src/store/Store.ts index 3ec166e91..888f4660d 100644 --- a/packages/pinia-orm/src/store/Store.ts +++ b/packages/pinia-orm/src/store/Store.ts @@ -19,8 +19,6 @@ export interface CacheConfigOptions { export interface InstallOptions { model?: ModelConfigOptions cache?: CacheConfigOptions | boolean - plugins: Record - [key: string]: any } export interface FilledInstallOptions { diff --git a/packages/pinia-orm/tests/unit/PiniaORM.spec.ts b/packages/pinia-orm/tests/unit/PiniaORM.spec.ts index ffee3bce4..0bfc24c17 100644 --- a/packages/pinia-orm/tests/unit/PiniaORM.spec.ts +++ b/packages/pinia-orm/tests/unit/PiniaORM.spec.ts @@ -124,7 +124,7 @@ describe('unit/PiniaORM', () => { @Str('') declare name: string @Str('') declare username: string } - createPiniaORM({ model: { nameSpace: 'orm' } }) + createPiniaORM({ model: { namespace: 'orm' } }) const userRepo = useRepo(User) const user = userRepo.save({ From ceb099833f06d038e1b6e201b39643cafb70bca9 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 12:15:09 +0200 Subject: [PATCH 11/20] refactor(pinia-orm): linting --- packages/pinia-orm/src/store/Plugins.ts | 4 ++-- packages/pinia-orm/src/store/Store.ts | 3 +-- packages/pinia-orm/tests/feature/plugin/plugin.test.ts | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/pinia-orm/src/store/Plugins.ts b/packages/pinia-orm/src/store/Plugins.ts index 6f14a812e..6357a5d42 100644 --- a/packages/pinia-orm/src/store/Plugins.ts +++ b/packages/pinia-orm/src/store/Plugins.ts @@ -13,9 +13,9 @@ export interface PiniaOrmPlugin { export const plugins: PiniaOrmPlugin[] = [] -export function registerPlugins(repository: Repository) { +export function registerPlugins (repository: Repository) { let config = globalConfig - plugins.forEach(plugin => { + plugins.forEach((plugin) => { const pluginConfig = plugin({ config, repository, model: repository.getModel() }) config = { ...config, ...pluginConfig.config } }) diff --git a/packages/pinia-orm/src/store/Store.ts b/packages/pinia-orm/src/store/Store.ts index 888f4660d..31ec3d279 100644 --- a/packages/pinia-orm/src/store/Store.ts +++ b/packages/pinia-orm/src/store/Store.ts @@ -1,4 +1,3 @@ -import type { PiniaPlugin } from 'pinia' import type { WeakCache } from '../cache/WeakCache' import type { Model } from '../model/Model' import { CONFIG_DEFAULTS, config } from './Config' @@ -37,7 +36,7 @@ export function createORM (options?: InstallOptions) { config.model = { ...CONFIG_DEFAULTS.model, ...options?.model } config.cache = options?.cache === false ? false : { ...CONFIG_DEFAULTS.cache, ...(options?.cache !== true && options?.cache) } const orm = { - use(plugin: PiniaOrmPlugin) { + use (plugin: PiniaOrmPlugin) { plugins.push(plugin) return this } diff --git a/packages/pinia-orm/tests/feature/plugin/plugin.test.ts b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts index 1a7960e97..fc0a21681 100644 --- a/packages/pinia-orm/tests/feature/plugin/plugin.test.ts +++ b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts @@ -1,8 +1,7 @@ import { describe, expect, it } from 'vitest' -import { Model, useRepo } from '../../../src' +import { Model, useRepo, PiniaOrmPlugin } from '../../../src' import { Attr, Str } from '../../../src/decorators' import { createPiniaORM } from '../../helpers' -import { PiniaOrmPlugin } from '../../../src' describe('feature/plugin/plugin', () => { class User extends Model { From e5703141086613a3dfee00ecaf837ade57d9e46a Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 12:19:58 +0200 Subject: [PATCH 12/20] refactor(pinia-orm): fix build typings --- packages/pinia-orm/src/model/Model.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pinia-orm/src/model/Model.ts b/packages/pinia-orm/src/model/Model.ts index 71c284305..1ce74bfad 100644 --- a/packages/pinia-orm/src/model/Model.ts +++ b/packages/pinia-orm/src/model/Model.ts @@ -101,7 +101,7 @@ export class Model { /** * The global install options */ - static config: ModelConfigOptions + static config: ModelConfigOptions & { [key: string]: any } /** * The type key for the model. @@ -603,7 +603,7 @@ export class Model { /** * Get the model config. */ - $config (): ModelConfigOptions { + $config (): ModelConfigOptions & { [key: string]: any } { return this.$self().config } From d1adff6c983ddfeedb64526e12676065b57939aa Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 12:23:39 +0200 Subject: [PATCH 13/20] refactor(pinia-orm): adjust size --- packages/pinia-orm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pinia-orm/package.json b/packages/pinia-orm/package.json index b160ff1b9..3cbbe47b3 100644 --- a/packages/pinia-orm/package.json +++ b/packages/pinia-orm/package.json @@ -97,7 +97,7 @@ "size-limit": [ { "path": "dist/index.mjs", - "limit": "12 kB" + "limit": "13 kB" }, { "path": "dist/decorators.mjs", From e7310d093b1c88e8a20fa6ddc98b82841c39255d Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 9 Sep 2023 12:25:16 +0200 Subject: [PATCH 14/20] refactor(axios): linting --- packages/axios/test/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/axios/test/setup.ts b/packages/axios/test/setup.ts index 0dc6bbea1..d40cef284 100644 --- a/packages/axios/test/setup.ts +++ b/packages/axios/test/setup.ts @@ -18,7 +18,7 @@ beforeEach(() => { const pinia = createPinia() const piniaOrm = createORM() piniaOrm().use(piniaOrmPluginAxios({ - axios: axios + axios })) pinia.use(piniaOrm) app.use(pinia) From f6a55007c09d251e1145328907ac8b39035b285b Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Tue, 12 Sep 2023 22:58:07 +0200 Subject: [PATCH 15/20] test(axios): add test for extending repository --- packages/pinia-orm/src/repository/Repository.ts | 1 + .../pinia-orm/tests/feature/plugin/plugin.test.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/pinia-orm/src/repository/Repository.ts b/packages/pinia-orm/src/repository/Repository.ts index b33da50b2..e7e8da503 100644 --- a/packages/pinia-orm/src/repository/Repository.ts +++ b/packages/pinia-orm/src/repository/Repository.ts @@ -24,6 +24,7 @@ import { config as globalConfig } from '../store/Config' import { FilledInstallOptions } from '@/store/Store' export class Repository { + [index: string]: any /** * A special flag to indicate if this is the repository class or not. It's * used when retrieving repository instance from `store.$repo()` method to diff --git a/packages/pinia-orm/tests/feature/plugin/plugin.test.ts b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts index fc0a21681..b1adf231b 100644 --- a/packages/pinia-orm/tests/feature/plugin/plugin.test.ts +++ b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts @@ -24,4 +24,17 @@ describe('feature/plugin/plugin', () => { expect(userRepo.config.apiConfig).toBe('test') }) + + it('can extend repository', () => { + const plugin: PiniaOrmPlugin = (context) => { + context.repository.test = () => { return 'test' } + return context + } + + createPiniaORM(undefined, [plugin]) + + const userRepo = useRepo(User) + + expect(userRepo.test()).toBe('test') + }) }) From 7cc1e1ec3d7098c6601926c462c12e357b70c58d Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Fri, 15 Sep 2023 17:21:31 +0200 Subject: [PATCH 16/20] feat(axios): add first docs & add `define` function for plugins --- .../1.guide/1.getting-started/2.usage.md | 4 -- docs/content/3.plugins/_dir.yml | 1 + docs/content/3.plugins/axios/setup.md | 17 ++++++++ docs/content/3.plugins/introduction.md | 40 +++++++++++++++++++ .../{3.changelog.md => 4.changelog.md} | 0 packages/axios/src/plugin.ts | 6 +-- .../pinia-orm/src/repository/Repository.ts | 2 +- packages/pinia-orm/src/schema/Schema.ts | 1 - packages/pinia-orm/src/store/Plugins.ts | 2 + packages/pinia-orm/src/support/Utils.ts | 2 +- .../tests/feature/plugin/plugin.test.ts | 10 ++--- .../eager_loads/eager_loads_all.spec.ts | 2 - 12 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 docs/content/3.plugins/_dir.yml create mode 100644 docs/content/3.plugins/axios/setup.md create mode 100644 docs/content/3.plugins/introduction.md rename docs/content/{3.changelog.md => 4.changelog.md} (100%) diff --git a/docs/content/1.guide/1.getting-started/2.usage.md b/docs/content/1.guide/1.getting-started/2.usage.md index 40178d586..6e55cba58 100644 --- a/docs/content/1.guide/1.getting-started/2.usage.md +++ b/docs/content/1.guide/1.getting-started/2.usage.md @@ -1,7 +1,3 @@ ---- -description: '' ---- - # Usage ## Standard diff --git a/docs/content/3.plugins/_dir.yml b/docs/content/3.plugins/_dir.yml new file mode 100644 index 000000000..0ebe984cb --- /dev/null +++ b/docs/content/3.plugins/_dir.yml @@ -0,0 +1 @@ +icon: heroicons-outline:bookmark-alt diff --git a/docs/content/3.plugins/axios/setup.md b/docs/content/3.plugins/axios/setup.md new file mode 100644 index 000000000..a7f4fed6b --- /dev/null +++ b/docs/content/3.plugins/axios/setup.md @@ -0,0 +1,17 @@ +# Setup Axios + +This plugin gives you useful functions which is extending the `Repository` + +## Install Pinia ORM Axios Plugin + +::code-group + ```bash [Yarn] + yarn add @pinia-orm/axios + ``` + ```bash [NPM] + npm install @pinia-orm/axios --save + ``` + ```bash [PNPM] + pnpm add @pinia-orm/axios + ``` +:: diff --git a/docs/content/3.plugins/introduction.md b/docs/content/3.plugins/introduction.md new file mode 100644 index 000000000..5b57e11eb --- /dev/null +++ b/docs/content/3.plugins/introduction.md @@ -0,0 +1,40 @@ +# Introduction + +Pinia ORM can also be extended by a plugin system which you can use to extend the +`Repository` or the global `config` + +## Writing a custom plugin + +Use `definePiniaOrmPlugin` to create a custom Pinia ORM plugin. The context option gives you +`model`, `repository` and `config` which you can edit + +````ts{piniaOrmPlugin.ts} +export default definePiniaOrmPlugin((context) => { + context.config.apiConfig = 'test' + return context +}) +```` + +Now add your custom plugin to the Pinia ORM instance: + +````ts +import { createPinia, setActivePinia } from 'pinia' +import { createORM } from 'pinia-orm' +import { createApp } from 'vue' +import { piniaOrmPlugin } from './plugins' + +const app = createApp({}) +const pinia = createPinia() +const piniaOrm = createORM() +piniaOrm().use(piniaOrmPlugin) +pinia.use(piniaOrm) +app.use(pinia) +setActivePinia(pinia) +```` + +Now everytime e.g. you use `useRepo` which uses the global config you can do this + +````ts +console.log(useRepo(User).config.apiConfig) +// 'test' +```` diff --git a/docs/content/3.changelog.md b/docs/content/4.changelog.md similarity index 100% rename from docs/content/3.changelog.md rename to docs/content/4.changelog.md diff --git a/packages/axios/src/plugin.ts b/packages/axios/src/plugin.ts index c93872aaf..b8ae19c75 100644 --- a/packages/axios/src/plugin.ts +++ b/packages/axios/src/plugin.ts @@ -1,9 +1,9 @@ -import { PiniaOrmPlugin } from 'pinia-orm' +import { PiniaOrmPlugin, definePiniaOrmPlugin } from 'pinia-orm' import { GlobalConfig } from './types/config' export function piniaOrmPluginAxios (axiosConfig?: GlobalConfig): PiniaOrmPlugin { - return (context) => { + return definePiniaOrmPlugin((context) => { context.config.axiosApi = axiosConfig return context - } + }) } diff --git a/packages/pinia-orm/src/repository/Repository.ts b/packages/pinia-orm/src/repository/Repository.ts index e7e8da503..5b541427c 100644 --- a/packages/pinia-orm/src/repository/Repository.ts +++ b/packages/pinia-orm/src/repository/Repository.ts @@ -21,7 +21,7 @@ import { cache } from '../cache/SharedWeakCache' import { cache as hydratedDataCache } from '../cache/SharedHydratedDatakCache' import type { WeakCache } from '../cache/WeakCache' import { config as globalConfig } from '../store/Config' -import { FilledInstallOptions } from '@/store/Store' +import { FilledInstallOptions } from '../store/Store' export class Repository { [index: string]: any diff --git a/packages/pinia-orm/src/schema/Schema.ts b/packages/pinia-orm/src/schema/Schema.ts index de4799ac2..9dc2bd5d7 100644 --- a/packages/pinia-orm/src/schema/Schema.ts +++ b/packages/pinia-orm/src/schema/Schema.ts @@ -113,7 +113,6 @@ export class Schema { // If the `key` is not `null`, that means this record is a nested // relationship of the parent model. In this case, we'll attach any // missing foreign keys to the record first. - console.log('schema', parent.$fields(), key, model.$entity(), parent.$entity()) if (key !== null) { (parent.$fields()[key] as Relation)?.attach(parentRecord, record) } // Next, we'll generate any missing primary key fields defined as diff --git a/packages/pinia-orm/src/store/Plugins.ts b/packages/pinia-orm/src/store/Plugins.ts index 6357a5d42..1a3b45f5c 100644 --- a/packages/pinia-orm/src/store/Plugins.ts +++ b/packages/pinia-orm/src/store/Plugins.ts @@ -11,6 +11,8 @@ export interface PiniaOrmPlugin { (context: PiniaOrmPluginContext): PiniaOrmPluginContext } +export const definePiniaOrmPlugin = (plugin: PiniaOrmPlugin) => plugin + export const plugins: PiniaOrmPlugin[] = [] export function registerPlugins (repository: Repository) { diff --git a/packages/pinia-orm/src/support/Utils.ts b/packages/pinia-orm/src/support/Utils.ts index 0c343b007..7f7a3dd43 100644 --- a/packages/pinia-orm/src/support/Utils.ts +++ b/packages/pinia-orm/src/support/Utils.ts @@ -1,4 +1,4 @@ -import type { Element } from '@' +import type { Element } from '../../src' interface SortableArray { criteria: any[] diff --git a/packages/pinia-orm/tests/feature/plugin/plugin.test.ts b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts index b1adf231b..e2cf6e40d 100644 --- a/packages/pinia-orm/tests/feature/plugin/plugin.test.ts +++ b/packages/pinia-orm/tests/feature/plugin/plugin.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { Model, useRepo, PiniaOrmPlugin } from '../../../src' +import { Model, useRepo, definePiniaOrmPlugin } from '../../../src' import { Attr, Str } from '../../../src/decorators' import { createPiniaORM } from '../../helpers' @@ -13,10 +13,10 @@ describe('feature/plugin/plugin', () => { } it('can add extra config to the configuration', () => { - const plugin: PiniaOrmPlugin = (context) => { + const plugin = definePiniaOrmPlugin((context) => { context.config.apiConfig = 'test' return context - } + }) createPiniaORM(undefined, [plugin]) @@ -26,10 +26,10 @@ describe('feature/plugin/plugin', () => { }) it('can extend repository', () => { - const plugin: PiniaOrmPlugin = (context) => { + const plugin = definePiniaOrmPlugin((context) => { context.repository.test = () => { return 'test' } return context - } + }) createPiniaORM(undefined, [plugin]) diff --git a/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts b/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts index e7fe3e217..f6f4ad0da 100644 --- a/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts +++ b/packages/pinia-orm/tests/feature/relations/eager_loads/eager_loads_all.spec.ts @@ -73,8 +73,6 @@ describe('feature/relations/eager_loads_all', () => { const posts = postsRepo.withAllRecursive().get() const onlyPosts2 = postsRepo.all() - console.log(posts, onlyPosts) - assertModels(posts, [{ id: 1, userId: 1, From cda1219de6778f44f6784eb3c87f2d7aa52305cd Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Fri, 15 Sep 2023 17:37:32 +0200 Subject: [PATCH 17/20] docs(axios): continue writing --- .../{introduction.md => 1.introduction.md} | 0 docs/content/3.plugins/2.axios/setup.md | 49 +++++++++++++++++++ docs/content/3.plugins/axios/setup.md | 17 ------- 3 files changed, 49 insertions(+), 17 deletions(-) rename docs/content/3.plugins/{introduction.md => 1.introduction.md} (100%) create mode 100644 docs/content/3.plugins/2.axios/setup.md delete mode 100644 docs/content/3.plugins/axios/setup.md diff --git a/docs/content/3.plugins/introduction.md b/docs/content/3.plugins/1.introduction.md similarity index 100% rename from docs/content/3.plugins/introduction.md rename to docs/content/3.plugins/1.introduction.md diff --git a/docs/content/3.plugins/2.axios/setup.md b/docs/content/3.plugins/2.axios/setup.md new file mode 100644 index 000000000..57ab94b28 --- /dev/null +++ b/docs/content/3.plugins/2.axios/setup.md @@ -0,0 +1,49 @@ +# Setup Axios + +This plugin gives you useful functions which is extending the `Repository` + +## Install Pinia ORM Axios Plugin + +::code-group + ```bash [Yarn] + yarn add @pinia-orm/axios + ``` + ```bash [NPM] + npm install @pinia-orm/axios --save + ``` + ```bash [PNPM] + pnpm add @pinia-orm/axios + ``` +:: + +## Adding the plugin to your pinia ORM store + +::code-group + ```js{}[Vue3] + import { createPinia } from 'pinia' + import { createORM } from 'pinia-orm' + import axios form 'axios' + + const pinia = createPinia() + const piniaOrm = createORM() + piniaOrm().use(piniaOrmPluginAxios({ + axios + })) + pinia.use(piniaOrm) + ``` + ```js{}[Vue2] + import { createPinia, PiniaVuePlugin } from 'pinia' + import { createORM } from 'pinia-orm' + import axios form 'axios' + + Vue.use(PiniaVuePlugin) + const pinia = createPinia() + const piniaOrm = createORM() + piniaOrm().use(piniaOrmPluginAxios({ + axios + })) + pinia.use(piniaOrm) + ``` +:: + + diff --git a/docs/content/3.plugins/axios/setup.md b/docs/content/3.plugins/axios/setup.md deleted file mode 100644 index a7f4fed6b..000000000 --- a/docs/content/3.plugins/axios/setup.md +++ /dev/null @@ -1,17 +0,0 @@ -# Setup Axios - -This plugin gives you useful functions which is extending the `Repository` - -## Install Pinia ORM Axios Plugin - -::code-group - ```bash [Yarn] - yarn add @pinia-orm/axios - ``` - ```bash [NPM] - npm install @pinia-orm/axios --save - ``` - ```bash [PNPM] - pnpm add @pinia-orm/axios - ``` -:: From 98c2517b84acff6a23e0261754af4703c78a9035 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Fri, 15 Sep 2023 21:43:56 +0200 Subject: [PATCH 18/20] docs(axios): port more docs from vuex-orm axios --- .../2.axios/{setup.md => 1.guide/1.setup.md} | 9 +- .../2.axios/1.guide/2.configuration.md | 181 ++++++++++++++ .../3.plugins/2.axios/1.guide/2.usage.md | 222 ++++++++++++++++++ .../2.axios/1.guide/3.custom-actions.md | 93 ++++++++ .../3.plugins/2.axios/2.api/1.repository.md | 52 ++++ .../3.plugins/2.axios/2.api/2.request.md | 85 +++++++ .../3.plugins/2.axios/2.api/3.response.md | 59 +++++ packages/axios/README.md | 25 +- packages/axios/src/api/Response.ts | 30 +-- .../{useApiRepo.ts => useAxiosRepo.ts} | 2 +- packages/axios/src/index.ts | 2 +- packages/axios/src/plugin.ts | 4 +- .../axios/src/repository/AxiosRepository.ts | 5 + packages/axios/src/types/config.ts | 1 + packages/axios/test/feature/Request.spec.ts | 33 ++- .../test/feature/Request_Actions.spec.ts | 6 +- .../test/feature/Request_DataKey.spec.ts | 4 +- .../feature/Request_DataTransformer.spec.ts | 4 +- .../axios/test/feature/Request_Delete.spec.ts | 4 +- .../axios/test/feature/Request_Save.spec.ts | 4 +- .../test/feature/Response_Delete.spec.ts | 6 +- .../axios/test/feature/Response_Save.spec.ts | 8 +- packages/axios/test/setup.ts | 4 +- 23 files changed, 774 insertions(+), 69 deletions(-) rename docs/content/3.plugins/2.axios/{setup.md => 1.guide/1.setup.md} (66%) create mode 100644 docs/content/3.plugins/2.axios/1.guide/2.configuration.md create mode 100644 docs/content/3.plugins/2.axios/1.guide/2.usage.md create mode 100644 docs/content/3.plugins/2.axios/1.guide/3.custom-actions.md create mode 100644 docs/content/3.plugins/2.axios/2.api/1.repository.md create mode 100644 docs/content/3.plugins/2.axios/2.api/2.request.md create mode 100644 docs/content/3.plugins/2.axios/2.api/3.response.md rename packages/axios/src/composables/{useApiRepo.ts => useAxiosRepo.ts} (75%) diff --git a/docs/content/3.plugins/2.axios/setup.md b/docs/content/3.plugins/2.axios/1.guide/1.setup.md similarity index 66% rename from docs/content/3.plugins/2.axios/setup.md rename to docs/content/3.plugins/2.axios/1.guide/1.setup.md index 57ab94b28..3cfe8efd6 100644 --- a/docs/content/3.plugins/2.axios/setup.md +++ b/docs/content/3.plugins/2.axios/1.guide/1.setup.md @@ -18,15 +18,19 @@ This plugin gives you useful functions which is extending the `Repository` ## Adding the plugin to your pinia ORM store +You have to options here to use the plugin. Either you use `createPiniaOrmPluginAxios(options?: PinaOrmPluginOptions)` +or you use `pinaOrmPluginAxios`. It depends if you want to pass options on initialization or later. + ::code-group ```js{}[Vue3] import { createPinia } from 'pinia' import { createORM } from 'pinia-orm' + import { createPiniaOrmPluginAxios } from '@pinia-orm/axios' import axios form 'axios' const pinia = createPinia() const piniaOrm = createORM() - piniaOrm().use(piniaOrmPluginAxios({ + piniaOrm().use(createPiniaOrmPluginAxios({ axios })) pinia.use(piniaOrm) @@ -34,12 +38,13 @@ This plugin gives you useful functions which is extending the `Repository` ```js{}[Vue2] import { createPinia, PiniaVuePlugin } from 'pinia' import { createORM } from 'pinia-orm' + import { createPiniaOrmPluginAxios } from '@pinia-orm/axios' import axios form 'axios' Vue.use(PiniaVuePlugin) const pinia = createPinia() const piniaOrm = createORM() - piniaOrm().use(piniaOrmPluginAxios({ + piniaOrm().use(createPiniaOrmPluginAxios({ axios })) pinia.use(piniaOrm) diff --git a/docs/content/3.plugins/2.axios/1.guide/2.configuration.md b/docs/content/3.plugins/2.axios/1.guide/2.configuration.md new file mode 100644 index 000000000..5bd034c5c --- /dev/null +++ b/docs/content/3.plugins/2.axios/1.guide/2.configuration.md @@ -0,0 +1,181 @@ +# Configurations + +Pinia ORM Axios plugin comes with various options to control request behavior. These options can be configured in three common places: + +- **Globally** - options can defined during installation +- **Model** - options can be defined on a per-model basis +- **Request** - options can be defined on a per-request basis + +Any axios options are permitted alongside any plugin options. Options are inherited in the same order, i.e. Global configuration is merged and preceded by Model configuration which is then merged and preceded by any Request configuration. + +### Global Configuration + +Options can be defined during plugin installation by passing an object as the second argument of the Pinia ORM `use()` method. At minimum, the axios instance is required while any other option is entirely optional. + +The following example configures the plugin with an axios instance (required), the `baseURL` option, and some common `headers` that all requests will inherit: + +```js +import axios from 'axios' +import { createORM } from 'pinia-orm' +import { createPiniaOrmPluginAxios } from '@pinia-orm/axios' + +const piniaOrm = createORM() +piniaOrm().use(createPiniaOrmPluginAxios({ + axios, + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + baseURL: 'https://example.com/api/' +})) +``` + +### Model Configuration + +Options can be defined on models by setting the static `config.axiosApi` property. This is an object where you may configure model-level request configuration. + +The following example configures a model with common `headers` and `baseURL` options: + +```js +import { Model } from 'pinia-orm' + +class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + + static config = { + axiosApi: { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + baseURL: 'https://example.com/api/' + } + } +} +``` + +### Request Configuration + +Options can be defined on a per-request basis. These options will inherit any global and model configurations which are subsequently passed on to the request. + +The following example configures a request with common `headers` and `baseURL` options: + +```js +useAxiosRepo(User).api().get('/api/users', { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + baseURL: 'https://example.com/api/' +}) +``` + +Request configurations vary depending on the type of request being made. Please refer to the [Usage Guide](usage) to read more. + + +## Available Options + +In addition to [axios request options](https://github.com/axios/axios#request-config), the plugin can be configured with the following options: + +### `dataKey` + +- **Type**: `string` +- **Default**: `undefined` + + This option will inform the plugin of the resource key your elements may be nested under in the response body. + + For example, your response body may be nested under a resource key called `data`: + + ```js + { + ok: true, + data: { + id: 1, + name: 'John Doe' + } + } + ``` + + The following example sets the `dataKey` to `data` as this is the resource key which contains the required data for the model schema. + + ```js + useAxiosRepo(User).api().get('/api/users', { + dataKey: 'data' + }) + ``` + + The plugin will pass all the data within the data object to Pinia ORM which can then be successfully persisted to the store. + + ::alert{type=warning} + This option is ignored when using the `dataTransformer` option. + :: + +### `dataTransformer` + +- **Type**: `(response: AxiosResponse) => Array | Object` +- **Default**: `undefined` + + This option will let you intercept and transform the response before persisting it to the store. + + The method will receive a [Response](usage.md#handling-responses) object allowing you to access response properties such as response headers, as well as manipulate the data as you see fit. + + Any method defined must return data to pass on to Pinia ORM. + + You can also use object destructuring to get specific properties from the response object. + + ```js + useAxiosRepo(User).api().get('/api/users', { + dataTransformer: ({ data, headers }) => { + // Do stuff with headers... + // Do stuff with data... + + return data + } + }) + ``` + + ::: warning + Using the `dataTransformer` option will ignore any `dataKey` option. + ::: + + **See also**: [Transforming Data](usage.md#transforming-data) + +### `persistBy` + +- **Type**: `string` +- **Default**: `'save'` + + This option determines which Pinia ORM persist method should be called when Pinia ORM Axios attempts to save the response data to the store. + + You can set this option to any one of the following string values: + + - `insert` + +### `save` + +- **Type**: `boolean` +- **Default**: `true` + + This option will determine whether Pinia ORM should persist the response data to the store or not. + + By setting this option to `false`, the response data will not be persisted and you will have to handle persistence alternatively. The `entities` property in the [Response](usage.md#handling-responses) object will also be `null` since it will no longer be persisting data automatically. + + **See also**: [Deferring Persistence](usage.md#deferring-persistence) + +### `delete` + +- **Type**: `string | number | (model: Model) => boolean` +- **Default**: `undefined` + + This option is primarily used with delete requests. It's argument signature is identical to the [Pinia ORM delete](https://vuex-orm.org/guide/data/deleting) method by which a primary key can be set as the value, or passing a predicate function. The corresponding record will be removed from the store after the request is made. + + Setting this option will ignore any `save` options you may have set and therefore persistence is not possible when using this option. + + **See also**: [Delete Requests](usage.md#delete-requests) + +### `actions` + +- **Type**: `Object` +- **Default**: `{}` + + This option allows for your own predefined api methods. + + Please refer to the [Custom Actions](custom-actions) documentation to learn more. diff --git a/docs/content/3.plugins/2.axios/1.guide/2.usage.md b/docs/content/3.plugins/2.axios/1.guide/2.usage.md new file mode 100644 index 000000000..a796880af --- /dev/null +++ b/docs/content/3.plugins/2.axios/1.guide/2.usage.md @@ -0,0 +1,222 @@ +# Usage + +Pinia ORM Axios adds a new repository composable `useAxiosRepo` with an asynchronous method `api()`, when called, instantiates a new axios request for a repository. From these requests, repositories are able to persist data to the store automatically. + +For example, a `useAxiosRepo(User)` model may typically want to fetch all users and persist the response to the store. Pinia ORM Axios can achieve this by performing a simple request: + +```js +await useAxiosRepo(User).api().get('https://example.com/api/users') +``` + +## Performing Requests + +Pinia ORM Axios supports the most commonly used [axios request methods](https://github.com/axios/axios#request-method-aliases). These methods accept the same argument signature as their axios counterparts with the exception that the config can be expanded with additional plugin [options](configurations). + +### Supported Methods + +Here is a list of supported request methods: + +```js +useAxiosRepo(User).api().get(url, config) +useAxiosRepo(User).api().post(url, data, config) +useAxiosRepo(User).api().put(url, data, config) +useAxiosRepo(User).api().patch(url, data, config) +useAxiosRepo(User).api().delete(url, config) +useAxiosRepo(User).api().request(config) +``` + +Arguments given are passed on to the corresponding axios request method. + +- `url` is the server URL that will be used for the request. +- `data` is the data to be sent as the request body (where applicable). +- `config` is the plugin [config options](configurations) and also any valid [axios request config](https://github.com/axios/axios#request-config) options. + +### Request Configuration + +You can pass any of the plugin's options together with any axios request options for a request method. + +For example, let's configure the following `get` request: + +```js +useAxiosRepo(User).api().get('/api/users', { + baseURL: 'https://example.com/', + dataKey: 'result' +}) +``` + +The [`baseURL`](https://github.com/axios/axios#request-config) is an axios request option which will be prepended to the request URL (unless the URL is absolute). + +The [`dataKey`](configurations.md#datakey) is a plugin option which informs the plugin of the resource key your elements may be nested under in the response body. + +> Please refer to the list of [supported request methods](#supported-methods) above to determine where the `config` argument can be given in the corresponding request method. + +**See also**: [Configurations](configurations) + +### Persisting Response Data + +By default, the response data from a request is automatically saved to the store corresponding to the model the request is made on. + +For example, let's perform a basic `get` request on a `useAxiosRepo(User)` model: + +```js +useAxiosRepo(User).api().get('https://example.com/api/users') +``` + +The response body of the request may look like the following: + +```json +[ + { + "id": 1, + "name": "John Doe", + "age": 24 + }, + { + "id": 2, + "name": "Jane Doe", + "age": 21 + } +] +``` + +Pinia ORM Axios will automatically save this data to the store, and the users entity in the store may now look like the following: + +```js +{ + users: { + data: { + 1: { id: 1, name: 'John Doe', age: 24 }, + 2: { id: 2, name: 'Jane Doe', age: 21 } + } + } +} +``` + +Under the hood, the plugin will persist data to the store by determining which records require inserting and which require updating. + +If you do not want to persist response data automatically, you can defer persistence by configuring the request with the `{ save: false }` option. + +You may configure Pinia ORM Axios to persist data using an alternative Pinia ORM persist method other than the default `save`. For example, you can use `insert`: + +```js +useAxiosRepo(User).api().get('/api/users', { persistBy: 'insert' }) +``` + +**See also**: + +- [Deferring Persistence](#deferring-persistence) +- [Pinia ORM - Inserting & Updating](https://vuex-orm.org/guide/data/inserting-and-updating.html#insert-or-update) + +### Delete Requests + +::: warning +When performing a `delete` request, the plugin will not remove the corresponding entities from the store. It is not always possible to determine which record is to be deleted and often HTTP DELETE requests are performed on a resource URL. +::: + +If you want to delete a record from the store after performing a delete request, you must pass the `delete` option. + +```js +useAxiosRepo(User).api().delete('/api/users/1', { + delete: 1 +}) +``` + +**See also**: [Configurations - delete](configurations.md#delete) + +## Handling Responses + +Every request performed will return a `Response` object as the resolved value. This object is responsible for carrying and handling the response body and ultimately executing actions such as persisting data to the store. + +The `Response` object contains two noteworthy properties: + +- `entities` is the list of entities persisted to the store by Pinia ORM. +- `response` is the original [axios response schema](https://github.com/axios/axios#response-schema). + +You may access these properties through the returned value: + +```js +const result = await useAxiosRepo(User).api().get('/api/users') + +// Retrieving the response status. +result.response.status // 200 + +// Entities persisted to the store from the response body. +result.entities // { users: [{ ... }] } +``` + +**See also**: [API Reference - Response](../api/response.md) + +### Transforming Data + +You can configure the plugin to perform transformation on the response data, using the `dataTransformer` configuration option, before it is persisted to the store. + +For example, your API response may conform to the [JSON:API](https://jsonapi.org/) specification but may not match the schema for your `useAxiosRepo(User)` model. In such cases you may want to reformat the response data in a manner in which Pinia ORM can normalize. + +The `dataTransformer` method can also be used to hook into response data before it is persisted to the store, allowing you to access other response properties such as response headers, as well as manipulate the data as you see fit. + +To demonstrate how you may use this option, let's assume your response body looks like this: + +```js +{ + ok: true, + record: { + id: 1, + name: 'John Doe' + } +} +``` + +The following example intercepts the response using a `dataTransformer` method to extract the data to be persisted from the nested property. + +```js +useAxiosRepo(User).api().get('/api/users', { + dataTransformer: (response) => { + return response.data.record + } +}) +``` + +**See also**: [Configurations - dataTransformer](configurations.md#datatransformer) + +### Deferring Persistence + +By default, the response data from a request is automatically saved to the store but this may not always be desired. + +To prevent persisting data to the store, define and set the `save` option to `false`. The `Response` object conveniently provides `save()` method which allows you to persist the data at any time. + +For example, you might want to check if the response contains any errors: + +```js +// Prevent persisting response data to the store. +const result = await useAxiosRepo(User).api().get('/api/users', { + save: false +}) + +// Throw an error. +if (result.response.data.error) { + throw new Error('Something is wrong.') +} + +// Otherwise continue to persist to the store. +result.save() +``` + +When deferring persistence you can also determine whether the response data has been persisted to the store using the convenient `isSaved` property: + +```js +const result = await useAxiosRepo(User).api().get('/api/users', { + save: false +}) + +result.isSaved // false + +await result.save() + +result.isSaved // true +``` + +**See also**: + +- [Configurations - save](configurations.md#save) +- [API Reference - Response - save()](../api/response.md#save) +- [API Reference - Response - isSaved](../api/response.md#issaved) diff --git a/docs/content/3.plugins/2.axios/1.guide/3.custom-actions.md b/docs/content/3.plugins/2.axios/1.guide/3.custom-actions.md new file mode 100644 index 000000000..b6b462219 --- /dev/null +++ b/docs/content/3.plugins/2.axios/1.guide/3.custom-actions.md @@ -0,0 +1,93 @@ +# Custom Actions + +The Custom Actions lets you define your own predefined api methods. You can define any number of custom actions through your Model configurations through `actions` option. + +```js +class User extends Model { + static entity = 'users' + + static fields () { + return { + id: this.attr(null), + name: this.attr('') + } + } + + static config = { + axiosApi: { + actions: { + fetch: { + method: 'get', + url: '/api/users' + } + } + } + } +} +``` + +You can see that in the above example, we have defined `fetch` action with the option that defines the `method` and `url`. + +Now you may call this action as if it was a predefined method. + +```js +useAxiosRepo(User).api().fetch() +``` + +The above method will perform api call to `/api/users` with `GET` method. Now the value for the action (in the example, which is the object that defines `method` and `url`) is the request configuration. Now you see that the above example is equivalent to calling: + +```js +useAxiosRepo(User).api().request({ + method: 'get', + url: '/api/users' +}) +``` + +Actions can also be defined as a function. In this case, just call the desired method with in action. With this approach, you can configure the convenience argument to the action, and gives you more powerful control. + +Remember that inside the function, `this` is bind to the Request object, not the Model where the actions are defined. + +```js +class User extends Model { + static config = { + axiosApi: { + actions: { + fetchById (id) { + return this.get(`/api/users/${id}`) + } + } + } + } +} +``` + +And you can call that action like so. + +```js +useAxiosRepo(User).api().fetchById(1) +``` + +## When to Use Custom Actions? + +While the custom actions are convenient and easy to set up, you can always define methods to the Repository directly to get pretty much the same result. But +if you don't have defined any custom `UserRepository` when its easier to use the model configuration. + +```js +class UserRepository extends AxiosRepository { + static fetchById (id) { + return this.api().get(`/api/users/${id}`) + } +} +``` + +In this case, you must call the method from Repository and not from `api()`. + +```js +useAxiosRepo(User).fetchById(1) +``` + +To be honest, this is a much better way to define custom methods in terms of simplicity and also better with type safety when using TypeScript. + +The benefits of defining custom actions inside the configuration are that you can put those methods under Request object, so it becomes more consistent when calling it from the Model. Also, it could be easier to share custom actions between different Models. + +It's up to you how to define custom actions. Though if you have any ideas or feedback, we're more than happy to hear it from you! diff --git a/docs/content/3.plugins/2.axios/2.api/1.repository.md b/docs/content/3.plugins/2.axios/2.api/1.repository.md new file mode 100644 index 000000000..8ea72a0b3 --- /dev/null +++ b/docs/content/3.plugins/2.axios/2.api/1.repository.md @@ -0,0 +1,52 @@ +--- +sidebarDepth: 2 +--- + +# Repository + +Pinia ORM Axios adds supporting properties and methods to the `Model` object. + +## Static Properties + +### `axios` + +- **Type**: `AxiosInstance | null` + + The axios instance which was either set during plugin installation or set using the [`setAxios`](#setaxios) method. Pinia ORM Axios will use this axios instance to perform requests. + +### `apiConfig` + +- **Type**: `Object` +- **Default**: `{}` + + The property that holds the model configuration for requests. + +### `globalApiConfig` + +- **Type**: `Object` + + The property that holds the global configuration. The value will be set automatically during the plugin installation process. + +::: warning WARNING +Do not mutate this property programmatically. +::: + +## Static Methods + +### `api` + +- `api(): Request` + + Return a newly created [Request](request) instance. + +### `setAxios` + +- `setAxios(axios: AxiosInstance): void` + + Set the axios instance manually. Typical setups will configure the axios instance during installation. However, in some cases (mostly with Nuxt), you may need to set the axios instance at a later stage. + + ::: warning IMPORTANT + If you omit the axios instance during installation, it's important that one is set using `setAxios` before any attempt to make an API request. + ::: + + **See also**: [Nuxt.js Integration](../guide/setup.md#nuxt-js-integration) diff --git a/docs/content/3.plugins/2.axios/2.api/2.request.md b/docs/content/3.plugins/2.axios/2.api/2.request.md new file mode 100644 index 000000000..05f38341f --- /dev/null +++ b/docs/content/3.plugins/2.axios/2.api/2.request.md @@ -0,0 +1,85 @@ +--- +sidebarDepth: 2 +--- + +# Request + +The Request object is returned when calling the `api()` method on a repository. This object is the foundation for Pinia ORM Axios and enables you to call many of the supported axios methods to perform an API request. Any [Custom Actions](../guide/custom-actions) will also be defined on the Request object. + +```js +const request = useAxiosRepo(User).api() +``` + +You can call request methods directly through chaining. + +```js +const response = useAxiosRepo(User).api().get() +``` + +## Constructor + +- `constructor(model: typeof Model)` + + By default, calling the `api()` method on a model will attach the model class to the Request object. + + You may also create a Request instance by passing a model as the constructors only param. + + ```js + import { Request } from '@pinia-orm/axios' + + const request = new Request(useAxiosRepo(User)) + ``` + +## Instance Properties + +### `model` + +- **Type**: `typeof Model` + + The model class that is attached to the Request instance. + +### `axios` + +- **Type**: `AxiosInstance` + + The axios instance that will be used to perform the request. + +## Instance Methods + +### `get` + +- `get(url: string, config: Config = {}): Promise` + + Performs a `GET` request. It takes the same arguments as the axios `get` method. + +### `post` + +- `post(url: string, data: any = {}, config: Config = {}): Promise` + + Performs a `POST` request. It takes the same arguments as the axios `post` method. + +### `put` + +- `put(url: string, data: any = {}, config: Config = {}): Promise` + + Performs a `PUT` request. It takes the same arguments as the axios `put` method. + +### `patch` + +- `patch(url: string, data: any = {}, config: Config = {}): Promise` + + Performs a `PATCH` request. It takes the same arguments as the axios `patch` method. + +### `delete` + +- `delete(url: string, config: Config = {}): Promise` + + Performs a `DELETE` request. It takes the same arguments as the axios `delete` method. + +### `request` + +- `request(config: Config): Promise` + + Performs a request with the given config options. Requests will default to `GET` if the `method` option is not specified. + + All request aliases call this method by merging the relevant configs. You may use this method if you are more familiar with using the axios API in favour of alias methods. diff --git a/docs/content/3.plugins/2.axios/2.api/3.response.md b/docs/content/3.plugins/2.axios/2.api/3.response.md new file mode 100644 index 000000000..68d2f275c --- /dev/null +++ b/docs/content/3.plugins/2.axios/2.api/3.response.md @@ -0,0 +1,59 @@ +--- +sidebarDepth: 2 +--- + +# Response + +API requests return a Response object. This is responsible for carrying and handling the response body and ultimately executing actions such as persisting data to the store. + +## Instance Properties + +### `response` + +- **Type**: `Object` + + The axios response schema. Please refer to the [axios documentation](https://github.com/axios/axios#response-schema) for more details. + +### `entities` + +- **Type**: `Array | null` + + The return value from the Pinia ORM persist method. + + **See also**: [Configurations - save](../guide/configurations.md#save) + +### `isSaved` + +- **Type**: `boolean` + + Set to `true` when response data has persisted to the store. + +### `model` + +- **Type**: `typeof Model` + + The model class that initially made the request. + +### `config` + +- **Type**: `Object` + + The configuration which was passed to the [Request](request) instance. + +## Instance Methods + +### `save` + +- `save(): Promise` + + Save response data to the store. + + **See also**: [Deferring Persistence](../guide/usage.md#deferring-persistence) + +### `delete` + +- `delete(): Promise` + + Delete record from the store after a request has completed. This method relies on the `delete` option and will throw an error if it is not set. + + **See also**: [Delete Requests](../guide/usage.md#delete-requests) diff --git a/packages/axios/README.md b/packages/axios/README.md index 239d9d8fb..b8d78e3ae 100644 --- a/packages/axios/README.md +++ b/packages/axios/README.md @@ -7,7 +7,7 @@ [![Github Actions CI][github-actions-ci-src]][github-actions-ci-href] [![License][license-src]][license-href] -> Intuitive, type safe and flexible ORM for Pinia based on [Vuex ORM Next](https://github.com/vuex-orm/vuex-orm-next) +> Pinia ORM Axios Plugin based on [Vuex ORM Axios](https://github.com/vuex-orm/axios) - [✨  Release Notes](https://pinia-orm.codedredd.de/changelog) - [📖  Documentation](https://pinia-orm.codedredd.de) @@ -18,20 +18,23 @@ - [One-time donation via PayPal](https://paypal.me/dredd1984) - [👾  Playground](https://pinia-orm-play.codedredd.de) - - - - +

+ + + +

## License +Made with ❤️ + [MIT](http://opensource.org/licenses/MIT) -[npm-version-src]: https://img.shields.io/npm/v/@pinia-orm/nuxt/latest.svg -[npm-version-href]: https://npmjs.com/package/@pinia-orm/nuxt -[npm-downloads-src]: https://img.shields.io/npm/dm/@pinia-orm/nuxt.svg -[npm-downloads-href]: https://npmjs.com/package/@pinia-orm/nuxt +[npm-version-src]: https://img.shields.io/npm/v/@pinia-orm/axios/latest.svg +[npm-version-href]: https://npmjs.com/package/@pinia-orm/axios +[npm-downloads-src]: https://img.shields.io/npm/dm/@pinia-orm/axios.svg +[npm-downloads-href]: https://npmjs.com/package/@pinia-orm/axios [github-actions-ci-src]: https://github.com/codedredd/pinia-orm/actions/workflows/build.yml/badge.svg [github-actions-ci-href]: https://github.com/codedredd/pinia-orm/actions?query=workflow%3Abuild -[license-src]: https://img.shields.io/npm/l/@pinia-orm/nuxt.svg -[license-href]: https://npmjs.com/package/@pinia-orm/nuxt +[license-src]: https://img.shields.io/npm/l/@pinia-orm/axios.svg +[license-href]: https://npmjs.com/package/@pinia-orm/axios diff --git a/packages/axios/src/api/Response.ts b/packages/axios/src/api/Response.ts index fb6117ab3..43dc07968 100644 --- a/packages/axios/src/api/Response.ts +++ b/packages/axios/src/api/Response.ts @@ -61,7 +61,7 @@ export class Response { console.warn( '[Vuex ORM Axios] The "persistBy" option configured is not a ' + 'recognized value. Response data will be persisted by the ' + - 'default `insertOrUpdate` method.' + 'default `save` method.' ) method = 'save' @@ -108,20 +108,20 @@ export class Response { /** * Get persist options if any set in config. */ - protected getPersistOptions (): PersistOptions | undefined { - const persistOptions = this.config.persistOptions - - if (!persistOptions || typeof persistOptions !== 'object') { - return - } - - return Object.keys(persistOptions) - .filter(this.validatePersistAction) // Filter to avoid polluting the payload. - .reduce((carry, key) => { - carry[key] = persistOptions[key] - return carry - }, {} as PersistOptions) - } + // protected getPersistOptions (): PersistOptions | undefined { + // const persistOptions = this.config.persistOptions + // + // if (!persistOptions || typeof persistOptions !== 'object') { + // return + // } + // + // return Object.keys(persistOptions) + // .filter(this.validatePersistAction) // Filter to avoid polluting the payload. + // .reduce((carry, key) => { + // carry[key] = persistOptions[key] + // return carry + // }, {} as PersistOptions) + // } /** * Validate the given data to ensure the Vuex ORM persist methods accept it. diff --git a/packages/axios/src/composables/useApiRepo.ts b/packages/axios/src/composables/useAxiosRepo.ts similarity index 75% rename from packages/axios/src/composables/useApiRepo.ts rename to packages/axios/src/composables/useAxiosRepo.ts index 6b6d4076d..11372dc05 100644 --- a/packages/axios/src/composables/useApiRepo.ts +++ b/packages/axios/src/composables/useAxiosRepo.ts @@ -1,7 +1,7 @@ import { useRepo, Model } from 'pinia-orm' import { AxiosRepository } from '../repository/AxiosRepository' -export function useApiRepo (model: M) { +export function useAxiosRepo (model: M) { AxiosRepository.useModel = model return useRepo(AxiosRepository) } diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index 01a55f774..d269a9017 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -2,5 +2,5 @@ export * from './api/Response' export * from './api/Request' export * from './repository/AxiosRepository' export * from './composables/useAxiosApi' -export * from './composables/useApiRepo' +export * from './composables/useAxiosRepo' export * from './plugin' diff --git a/packages/axios/src/plugin.ts b/packages/axios/src/plugin.ts index b8ae19c75..aef529358 100644 --- a/packages/axios/src/plugin.ts +++ b/packages/axios/src/plugin.ts @@ -1,9 +1,11 @@ import { PiniaOrmPlugin, definePiniaOrmPlugin } from 'pinia-orm' import { GlobalConfig } from './types/config' -export function piniaOrmPluginAxios (axiosConfig?: GlobalConfig): PiniaOrmPlugin { +export function createPiniaOrmAxios (axiosConfig?: GlobalConfig): PiniaOrmPlugin { return definePiniaOrmPlugin((context) => { context.config.axiosApi = axiosConfig return context }) } + +export const piniaOrmPluginAxios = createPiniaOrmAxios() diff --git a/packages/axios/src/repository/AxiosRepository.ts b/packages/axios/src/repository/AxiosRepository.ts index 5d6850fac..10ff6e4dd 100644 --- a/packages/axios/src/repository/AxiosRepository.ts +++ b/packages/axios/src/repository/AxiosRepository.ts @@ -11,4 +11,9 @@ export class AxiosRepository extends Repository { api () { return useAxiosApi(this) } + + setAxios (axios: AxiosInstance) { + this.axios = axios + return this + } } diff --git a/packages/axios/src/types/config.ts b/packages/axios/src/types/config.ts index d60c6b36b..01a445db6 100644 --- a/packages/axios/src/types/config.ts +++ b/packages/axios/src/types/config.ts @@ -9,6 +9,7 @@ export interface Config extends AxiosRequestConfig { dataKey?: string url?: string method?: string + data?: any dataTransformer?: (response: AxiosResponse) => Element | Element[] save?: boolean persistBy?: PersistMethods diff --git a/packages/axios/test/feature/Request.spec.ts b/packages/axios/test/feature/Request.spec.ts index 4339fc9e7..1c01fad52 100644 --- a/packages/axios/test/feature/Request.spec.ts +++ b/packages/axios/test/feature/Request.spec.ts @@ -2,8 +2,8 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { assertState, createPiniaORM } from '../helpers' +import { useAxiosRepo } from '../../src' describe('Feature - Request', () => { let mock: MockAdapter @@ -32,7 +32,7 @@ describe('Feature - Request', () => { { id: 2, name: 'Jane Doe' } ]) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().get('/api/users') @@ -52,7 +52,7 @@ describe('Feature - Request', () => { ] }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().get('/api/users', { dataKey: 'data' }) @@ -70,7 +70,7 @@ describe('Feature - Request', () => { { id: 2, name: 'Jane Doe' } ]) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().post('/api/users') @@ -90,7 +90,7 @@ describe('Feature - Request', () => { ] }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().post('/api/users', {}, { dataKey: 'data' }) @@ -108,7 +108,7 @@ describe('Feature - Request', () => { { id: 2, name: 'Jane Doe' } ]) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().put('/api/users') @@ -128,7 +128,7 @@ describe('Feature - Request', () => { ] }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().put('/api/users', {}, { dataKey: 'data' }) @@ -146,7 +146,7 @@ describe('Feature - Request', () => { { id: 2, name: 'Jane Doe' } ]) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().patch('/api/users') @@ -166,7 +166,7 @@ describe('Feature - Request', () => { ] }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().patch('/api/users', {}, { dataKey: 'data' }) @@ -184,7 +184,7 @@ describe('Feature - Request', () => { { id: 2, name: 'Jane Doe' } ]) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().delete('/api/users') @@ -204,7 +204,7 @@ describe('Feature - Request', () => { ] }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().delete('/api/users', { dataKey: 'data' }) @@ -217,17 +217,14 @@ describe('Feature - Request', () => { }) it('throws error if `axios` is not set', async () => { - mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) + const userStore = useAxiosRepo(User).setAxios(null) - const userStore = useApiRepo(User) - - const response = await userStore.api().get('/api/users') try { - await response.delete() + const axios = userStore.api().axios } catch (e) { expect(e.message).toBe( - '[Vuex ORM Axios] Could not delete records because the `delete` option is not set.' + '[Vuex ORM Axios] The axios instance is not registered. Please register the axios instance to the repository.' ) return diff --git a/packages/axios/test/feature/Request_Actions.spec.ts b/packages/axios/test/feature/Request_Actions.spec.ts index 9dc9337ac..09fc7d625 100644 --- a/packages/axios/test/feature/Request_Actions.spec.ts +++ b/packages/axios/test/feature/Request_Actions.spec.ts @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { useAxiosRepo } from '../../src' import type { Request, Response } from '../../src' describe('Feature - Request - Actions', () => { @@ -38,7 +38,7 @@ describe('Feature - Request - Actions', () => { mock.onGet('/users').reply(200, { id: 1, name: 'John' }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().fetch() @@ -73,7 +73,7 @@ describe('Feature - Request - Actions', () => { mock.onGet('/users').reply(200, { id: 1, name: 'John' }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().fetch('/users') diff --git a/packages/axios/test/feature/Request_DataKey.spec.ts b/packages/axios/test/feature/Request_DataKey.spec.ts index eac0936c4..7fc6a6134 100644 --- a/packages/axios/test/feature/Request_DataKey.spec.ts +++ b/packages/axios/test/feature/Request_DataKey.spec.ts @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { useAxiosRepo } from '../../src' describe('Feature - Request - Data Key', () => { let mock: MockAdapter @@ -31,7 +31,7 @@ describe('Feature - Request - Data Key', () => { data: { id: 1, name: 'John Doe' } }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().request({ url: '/users', diff --git a/packages/axios/test/feature/Request_DataTransformer.spec.ts b/packages/axios/test/feature/Request_DataTransformer.spec.ts index 39855bd01..6c15039b6 100644 --- a/packages/axios/test/feature/Request_DataTransformer.spec.ts +++ b/packages/axios/test/feature/Request_DataTransformer.spec.ts @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { useAxiosRepo } from '../../src' describe('Feature - Request - Data Transformer', () => { let mock: MockAdapter @@ -31,7 +31,7 @@ describe('Feature - Request - Data Transformer', () => { data: { id: 1, name: 'John Doe' } }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) await userStore.api().request({ url: '/users', diff --git a/packages/axios/test/feature/Request_Delete.spec.ts b/packages/axios/test/feature/Request_Delete.spec.ts index 1665518e2..fc51615c5 100644 --- a/packages/axios/test/feature/Request_Delete.spec.ts +++ b/packages/axios/test/feature/Request_Delete.spec.ts @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { useAxiosRepo } from '../../src' describe('Feature - Request - Delete', () => { let mock: MockAdapter @@ -29,7 +29,7 @@ describe('Feature - Request - Delete', () => { it('can delete a record after the api call', async () => { mock.onDelete('/users/1').reply(200, { ok: true }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) userStore.save([ { id: 1, name: 'John' }, diff --git a/packages/axios/test/feature/Request_Save.spec.ts b/packages/axios/test/feature/Request_Save.spec.ts index 7e9d4fa8c..59aef8456 100644 --- a/packages/axios/test/feature/Request_Save.spec.ts +++ b/packages/axios/test/feature/Request_Save.spec.ts @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { useAxiosRepo } from '../../src' describe('Feature - Request - Save', () => { let mock: MockAdapter @@ -31,7 +31,7 @@ describe('Feature - Request - Save', () => { data: { id: 1, name: 'John Doe' } }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) const result = await userStore.api().request({ url: '/users', diff --git a/packages/axios/test/feature/Response_Delete.spec.ts b/packages/axios/test/feature/Response_Delete.spec.ts index 72a51d3eb..6b5aee956 100644 --- a/packages/axios/test/feature/Response_Delete.spec.ts +++ b/packages/axios/test/feature/Response_Delete.spec.ts @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { useAxiosRepo } from '../../src' describe('Feature - Response - Save', () => { let mock: MockAdapter @@ -29,7 +29,7 @@ describe('Feature - Response - Save', () => { it('can save response data manually', async () => { mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) const response = await userStore.api().get('/api/users') @@ -49,7 +49,7 @@ describe('Feature - Response - Save', () => { it('throws error if `delete` option is not set', async () => { mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) const response = await userStore.api().get('/api/users') diff --git a/packages/axios/test/feature/Response_Save.spec.ts b/packages/axios/test/feature/Response_Save.spec.ts index 2a8d6b225..b861c24a6 100644 --- a/packages/axios/test/feature/Response_Save.spec.ts +++ b/packages/axios/test/feature/Response_Save.spec.ts @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest' import { assertState } from '../helpers' -import { useApiRepo } from '../../src' +import { useAxiosRepo } from '../../src' describe('Feature - Response - Save', () => { let mock: MockAdapter @@ -31,7 +31,7 @@ describe('Feature - Response - Save', () => { spy.mockImplementation(x => x) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) mock.onGet('/api/users').reply(200, null) await userStore.api().get('/api/users') @@ -48,7 +48,7 @@ describe('Feature - Response - Save', () => { it('can save response data manually', async () => { mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) const response = await userStore.api().get('/api/users', { save: false }) @@ -66,7 +66,7 @@ describe('Feature - Response - Save', () => { it('sets `isSaved` flag', async () => { mock.onGet('/api/users').reply(200, { id: 1, name: 'John Doe' }) - const userStore = useApiRepo(User) + const userStore = useAxiosRepo(User) const response = await userStore.api().get('/api/users', { save: false }) diff --git a/packages/axios/test/setup.ts b/packages/axios/test/setup.ts index d40cef284..77e64cd7b 100644 --- a/packages/axios/test/setup.ts +++ b/packages/axios/test/setup.ts @@ -3,7 +3,7 @@ import { beforeAll, beforeEach } from 'vitest' import { Vue2, createApp, install, isVue2 } from 'vue-demi' import { Model, createORM, useRepo } from 'pinia-orm' import axios from 'axios' -import { piniaOrmPluginAxios } from '../src' +import { createPiniaOrmAxios } from '../src' beforeAll(() => { if (isVue2) { @@ -17,7 +17,7 @@ beforeEach(() => { const app = createApp({}) const pinia = createPinia() const piniaOrm = createORM() - piniaOrm().use(piniaOrmPluginAxios({ + piniaOrm().use(createPiniaOrmAxios({ axios })) pinia.use(piniaOrm) From cdbc29b1b1354b34a295235253905a06a48dfcc1 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Fri, 15 Sep 2023 21:49:25 +0200 Subject: [PATCH 19/20] refactor(axios): linting --- packages/axios/src/api/Response.ts | 2 +- packages/axios/test/feature/Request.spec.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/axios/src/api/Response.ts b/packages/axios/src/api/Response.ts index 43dc07968..0fca2e960 100644 --- a/packages/axios/src/api/Response.ts +++ b/packages/axios/src/api/Response.ts @@ -1,6 +1,6 @@ import type { AxiosResponse } from 'axios' import { Element, Collection } from 'pinia-orm' -import { Config, PersistMethods, PersistOptions } from '../types/config' +import { Config, PersistMethods } from '../types/config' import { AxiosRepository } from '../repository/AxiosRepository' export class Response { diff --git a/packages/axios/test/feature/Request.spec.ts b/packages/axios/test/feature/Request.spec.ts index 1c01fad52..cc75cd58a 100644 --- a/packages/axios/test/feature/Request.spec.ts +++ b/packages/axios/test/feature/Request.spec.ts @@ -2,7 +2,7 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { Model } from 'pinia-orm' import { describe, expect, it, beforeEach, afterEach } from 'vitest' -import { assertState, createPiniaORM } from '../helpers' +import { assertState } from '../helpers' import { useAxiosRepo } from '../../src' describe('Feature - Request', () => { @@ -216,12 +216,12 @@ describe('Feature - Request', () => { }) }) - it('throws error if `axios` is not set', async () => { + it('throws error if `axios` is not set', () => { const userStore = useAxiosRepo(User).setAxios(null) - try { const axios = userStore.api().axios + console.warn(axios) } catch (e) { expect(e.message).toBe( '[Vuex ORM Axios] The axios instance is not registered. Please register the axios instance to the repository.' From fe899c555153236481c14f9e8caf9360ed4324d0 Mon Sep 17 00:00:00 2001 From: Gregor Becker Date: Sat, 16 Sep 2023 17:38:41 +0200 Subject: [PATCH 20/20] refactor(axios): vuex typos --- .../content/app/AgoliaDocSearch.client.vue | 64 ------------------- packages/axios/src/api/Request.ts | 2 +- packages/axios/src/api/Response.ts | 12 ++-- packages/axios/test/feature/Request.spec.ts | 2 +- .../test/feature/Response_Delete.spec.ts | 2 +- 5 files changed, 9 insertions(+), 73 deletions(-) delete mode 100644 docs/components/content/app/AgoliaDocSearch.client.vue diff --git a/docs/components/content/app/AgoliaDocSearch.client.vue b/docs/components/content/app/AgoliaDocSearch.client.vue deleted file mode 100644 index 65c9f77ff..000000000 --- a/docs/components/content/app/AgoliaDocSearch.client.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - - - diff --git a/packages/axios/src/api/Request.ts b/packages/axios/src/api/Request.ts index ffef55e2a..e3f96d8be 100644 --- a/packages/axios/src/api/Request.ts +++ b/packages/axios/src/api/Request.ts @@ -36,7 +36,7 @@ export class Request { get axios (): AxiosInstance { if (!this.repository.axios) { throw new Error( - '[Vuex ORM Axios] The axios instance is not registered. Please register the axios instance to the repository.' + '[Pinia ORM Axios] The axios instance is not registered. Please register the axios instance to the repository.' ) } diff --git a/packages/axios/src/api/Response.ts b/packages/axios/src/api/Response.ts index 0fca2e960..a12eac1ff 100644 --- a/packages/axios/src/api/Response.ts +++ b/packages/axios/src/api/Response.ts @@ -20,7 +20,7 @@ export class Response { response: AxiosResponse /** - * Entities created by Vuex ORM. + * Entities created by Pinia ORM. */ entities: Collection | null = null @@ -46,7 +46,7 @@ export class Response { if (!this.validateData(data)) { console.warn( - '[Vuex ORM Axios] The response data could not be saved to the store ' + + '[Pinia ORM Axios] The response data could not be saved to the store ' + 'because it is not an object or an array. You might want to use ' + '`dataTransformer` option to handle non-array/object response ' + 'before saving it to the store.' @@ -59,7 +59,7 @@ export class Response { if (!this.validatePersistAction(method)) { console.warn( - '[Vuex ORM Axios] The "persistBy" option configured is not a ' + + '[Pinia ORM Axios] The "persistBy" option configured is not a ' + 'recognized value. Response data will be persisted by the ' + 'default `save` method.' ) @@ -80,7 +80,7 @@ export class Response { async delete (): Promise { if (this.config.delete === undefined) { throw new Error( - '[Vuex ORM Axios] Could not delete records because the `delete` option is not set.' + '[Pinia ORM Axios] Could not delete records because the `delete` option is not set.' ) } @@ -124,7 +124,7 @@ export class Response { // } /** - * Validate the given data to ensure the Vuex ORM persist methods accept it. + * Validate the given data to ensure the Pinia ORM persist methods accept it. */ protected validateData (data: any): data is Element | Element[] { return data !== null && typeof data === 'object' @@ -132,7 +132,7 @@ export class Response { /** * Validate the given string as to ensure it correlates with the available - * Vuex ORM persist methods. + * Pinia ORM persist methods. */ protected validatePersistAction (action: string): action is PersistMethods { return ['save', 'insert'].includes(action) diff --git a/packages/axios/test/feature/Request.spec.ts b/packages/axios/test/feature/Request.spec.ts index cc75cd58a..f99ae2e81 100644 --- a/packages/axios/test/feature/Request.spec.ts +++ b/packages/axios/test/feature/Request.spec.ts @@ -224,7 +224,7 @@ describe('Feature - Request', () => { console.warn(axios) } catch (e) { expect(e.message).toBe( - '[Vuex ORM Axios] The axios instance is not registered. Please register the axios instance to the repository.' + '[Pinia ORM Axios] The axios instance is not registered. Please register the axios instance to the repository.' ) return diff --git a/packages/axios/test/feature/Response_Delete.spec.ts b/packages/axios/test/feature/Response_Delete.spec.ts index 6b5aee956..b1cdbe019 100644 --- a/packages/axios/test/feature/Response_Delete.spec.ts +++ b/packages/axios/test/feature/Response_Delete.spec.ts @@ -57,7 +57,7 @@ describe('Feature - Response - Save', () => { await response.delete() } catch (e) { expect(e.message).toBe( - '[Vuex ORM Axios] Could not delete records because the `delete` option is not set.' + '[Pinia ORM Axios] Could not delete records because the `delete` option is not set.' ) return