diff --git a/packages/plugins/react/.eslintrc.json b/packages/plugins/react/.eslintrc.json deleted file mode 100644 index 0a913e874..000000000 --- a/packages/plugins/react/.eslintrc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ] -} diff --git a/packages/plugins/react/LICENSE b/packages/plugins/react/LICENSE deleted file mode 120000 index 5853aaea5..000000000 --- a/packages/plugins/react/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../../LICENSE \ No newline at end of file diff --git a/packages/plugins/react/README.md b/packages/plugins/react/README.md deleted file mode 100644 index ea63793af..000000000 --- a/packages/plugins/react/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# ZenStack React plugin & runtime - -**This package is deprecated. Please use `@zenstackhq/swr` or `@zenstackhq/tanstack-query` packages instead.** - -This package contains ZenStack plugin and runtime for ReactJS. - -Visit [Homepage](https://zenstack.dev) for more details. diff --git a/packages/plugins/react/jest.config.ts b/packages/plugins/react/jest.config.ts deleted file mode 100644 index 917cf52f6..000000000 --- a/packages/plugins/react/jest.config.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * For a detailed explanation regarding each configuration property and type check, visit: - * https://jestjs.io/docs/configuration - */ - -export default { - // Automatically clear mock calls, instances, contexts and results before every test - clearMocks: true, - - // Indicates whether the coverage information should be collected while executing the test - collectCoverage: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'tests/coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/', '/tests/'], - - // Indicates which provider should be used to instrument code for coverage - coverageProvider: 'v8', - - // A list of reporter names that Jest uses when writing coverage reports - coverageReporters: ['json', 'text', 'lcov', 'clover'], - - // A map from regular expressions to paths to transformers - transform: { '^.+\\.tsx?$': 'ts-jest' }, - - testTimeout: 300000, -}; diff --git a/packages/plugins/react/package.json b/packages/plugins/react/package.json deleted file mode 100644 index 40aeaeaee..000000000 --- a/packages/plugins/react/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "@zenstackhq/react", - "displayName": "ZenStack plugin and runtime for ReactJS", - "version": "1.0.0-beta.2", - "description": "ZenStack plugin and runtime for ReactJS", - "main": "index.js", - "repository": { - "type": "git", - "url": "https://github.com/zenstackhq/zenstack" - }, - "scripts": { - "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist", - "watch": "tsc --watch", - "lint": "eslint src --ext ts", - "prepublishOnly": "pnpm build", - "publish-dev": "pnpm publish --tag dev" - }, - "publishConfig": { - "directory": "dist", - "linkDirectory": true - }, - "keywords": [], - "author": "ZenStack Team", - "license": "MIT", - "dependencies": { - "@prisma/generator-helper": "^4.7.1", - "@zenstackhq/sdk": "workspace:*", - "change-case": "^4.1.2", - "decimal.js": "^10.4.2", - "lower-case-first": "^2.0.2", - "superjson": "^1.11.0", - "ts-morph": "^16.0.0", - "upper-case-first": "^2.0.2" - }, - "peerDependencies": { - "@tanstack/react-query": "4.x", - "react": "^17.0.2 || ^18", - "react-dom": "^17.0.2 || ^18", - "swr": "2.x" - }, - "devDependencies": { - "@tanstack/react-query": "^4.28.0", - "@types/jest": "^29.5.0", - "@types/lower-case-first": "^1.0.1", - "@types/react": "^18.0.26", - "@types/tmp": "^0.2.3", - "@types/upper-case-first": "^1.1.2", - "@zenstackhq/testtools": "workspace:*", - "copyfiles": "^2.4.1", - "jest": "^29.5.0", - "react": "^17.0.2 || ^18", - "react-dom": "^17.0.2 || ^18", - "rimraf": "^3.0.2", - "swr": "^2.0.3", - "ts-jest": "^29.0.5", - "typescript": "^4.9.4" - } -} diff --git a/packages/plugins/react/src/generator/index.ts b/packages/plugins/react/src/generator/index.ts deleted file mode 100644 index 2b4389756..000000000 --- a/packages/plugins/react/src/generator/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { DMMF } from '@prisma/generator-helper'; -import { PluginError, type PluginOptions } from '@zenstackhq/sdk'; -import type { Model } from '@zenstackhq/sdk/ast'; -import { generate as reactQueryGenerate } from './react-query'; -import { generate as swrGenerate } from './swr'; - -export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { - const fetcher = (options.fetcher as string) ?? 'swr'; - switch (fetcher) { - case 'swr': - return swrGenerate(model, options, dmmf); - case 'react-query': - return reactQueryGenerate(model, options, dmmf); - default: - throw new PluginError(options.name, `Unknown "fetcher" option: ${fetcher}, use "swr" or "react-query"`); - } -} diff --git a/packages/plugins/react/src/generator/react-query.ts b/packages/plugins/react/src/generator/react-query.ts deleted file mode 100644 index 6cbeb6f13..000000000 --- a/packages/plugins/react/src/generator/react-query.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { PluginOptions, createProject, getDataModels, getPrismaClientImportSpec, saveProject } from '@zenstackhq/sdk'; -import { DataModel, Model } from '@zenstackhq/sdk/ast'; -import { requireOption, resolvePath } from '@zenstackhq/sdk/utils'; -import { paramCase } from 'change-case'; -import { lowerCaseFirst } from 'lower-case-first'; -import * as path from 'path'; -import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; -import { upperCaseFirst } from 'upper-case-first'; - -export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { - let outDir = requireOption(options, 'output'); - outDir = resolvePath(outDir, options); - - const project = createProject(); - const warnings: string[] = []; - const models = getDataModels(model); - - generateIndex(project, outDir, models); - - models.forEach((dataModel) => { - const mapping = dmmf.mappings.modelOperations.find((op) => op.model === dataModel.name); - if (!mapping) { - warnings.push(`Unable to find mapping for model ${dataModel.name}`); - return; - } - generateModelHooks(project, outDir, dataModel, mapping); - }); - - await saveProject(project); - return warnings; -} - -function generateQueryHook( - sf: SourceFile, - model: string, - operation: string, - returnArray: boolean, - optionalInput: boolean, - overrideReturnType?: string, - overrideInputType?: string, - overrideTypeParameters?: string[] -) { - const capOperation = upperCaseFirst(operation); - - const argsType = overrideInputType ?? `Prisma.${model}${capOperation}Args`; - const inputType = `Prisma.SelectSubset`; - const returnType = - overrideReturnType ?? (returnArray ? `Array>` : `Prisma.${model}GetPayload`); - const optionsType = `UseQueryOptions<${returnType}>`; - - const func = sf.addFunction({ - name: `use${capOperation}${model}`, - typeParameters: overrideTypeParameters ?? [`T extends ${argsType}`], - parameters: [ - { - name: optionalInput ? 'args?' : 'args', - type: inputType, - }, - { - name: 'options?', - type: optionsType, - }, - ], - isExported: true, - }); - - func.addStatements([ - 'const { endpoint } = useContext(RequestHandlerContext);', - `return request.query<${returnType}>('${model}', \`\${endpoint}/${lowerCaseFirst( - model - )}/${operation}\`, args, options);`, - ]); -} - -function generateMutationHook( - sf: SourceFile, - model: string, - operation: string, - httpVerb: 'post' | 'put' | 'delete', - overrideReturnType?: string -) { - const capOperation = upperCaseFirst(operation); - - const argsType = `Prisma.${model}${capOperation}Args`; - const inputType = `Prisma.SelectSubset`; - const returnType = overrideReturnType ?? `Prisma.CheckSelect>`; - const nonGenericOptionsType = `Omit, 'mutationFn'>`; - const optionsType = `Omit, 'mutationFn'>`; - - const func = sf.addFunction({ - name: `use${capOperation}${model}`, - isExported: true, - parameters: [ - { - name: 'options?', - type: nonGenericOptionsType, - }, - { - name: 'invalidateQueries', - type: 'boolean', - initializer: 'true', - }, - ], - }); - func.addStatements(['const { endpoint } = useContext(RequestHandlerContext);']); - - func.addVariableStatement({ - declarationKind: VariableDeclarationKind.Const, - declarations: [ - { - name: `_mutation`, - initializer: ` - request.${httpVerb}Mutation<${argsType}, ${ - overrideReturnType ?? model - }>('${model}', \`\${endpoint}/${lowerCaseFirst(model)}/${operation}\`, options, invalidateQueries) - `, - }, - ], - }); - - func.addVariableStatement({ - declarationKind: VariableDeclarationKind.Const, - declarations: [ - { - name: 'mutation', - initializer: `{ - ..._mutation, - async mutateAsync( - args: Prisma.SelectSubset, - options?: ${optionsType} - ) { - return (await _mutation.mutateAsync( - args, - options as any - )) as ${returnType}; - }, - }`, - }, - ], - }); - - func.addStatements('return mutation;'); -} - -function generateModelHooks(project: Project, outDir: string, model: DataModel, mapping: DMMF.ModelMapping) { - const fileName = paramCase(model.name); - - const hooksFile = path.resolve(outDir, `${fileName}.ts`); - const sf = project.createSourceFile(hooksFile, undefined, { overwrite: true }); - - sf.addStatements('/* eslint-disable */'); - - const prismaImport = getPrismaClientImportSpec(model.$container, path.dirname(hooksFile)); - - sf.addImportDeclaration({ - namedImports: ['Prisma', model.name], - isTypeOnly: true, - moduleSpecifier: prismaImport, - }); - sf.addStatements([ - `import { useContext } from 'react';`, - `import { RequestHandlerContext } from '@zenstackhq/react/runtime';`, - `import * as request from '@zenstackhq/react/runtime/react-query';`, - `import type { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';`, - ]); - - // create is somehow named "createOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.create || (mapping as any).createOne) { - generateMutationHook(sf, model.name, 'create', 'post'); - } - - // createMany - if (mapping.createMany) { - generateMutationHook(sf, model.name, 'createMany', 'post', 'Prisma.BatchPayload'); - } - - // findMany - if (mapping.findMany) { - generateQueryHook(sf, model.name, 'findMany', true, true); - } - - // findUnique - if (mapping.findUnique) { - generateQueryHook(sf, model.name, 'findUnique', false, false); - } - - // findFirst - if (mapping.findFirst) { - generateQueryHook(sf, model.name, 'findFirst', false, true); - } - - // update - // update is somehow named "updateOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.update || (mapping as any).updateOne) { - generateMutationHook(sf, model.name, 'update', 'put'); - } - - // updateMany - if (mapping.updateMany) { - generateMutationHook(sf, model.name, 'updateMany', 'put', 'Prisma.BatchPayload'); - } - - // upsert - // upsert is somehow named "upsertOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.upsert || (mapping as any).upsertOne) { - generateMutationHook(sf, model.name, 'upsert', 'post'); - } - - // del - // delete is somehow named "deleteOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.delete || (mapping as any).deleteOne) { - generateMutationHook(sf, model.name, 'delete', 'delete'); - } - - // deleteMany - if (mapping.deleteMany) { - generateMutationHook(sf, model.name, 'deleteMany', 'delete', 'Prisma.BatchPayload'); - } - - // aggregate - if (mapping.aggregate) { - generateQueryHook(sf, model.name, 'aggregate', false, false, `Prisma.Get${model.name}AggregateType`); - } - - // groupBy - if (mapping.groupBy) { - const typeParameters = [ - `T extends Prisma.${model.name}GroupByArgs`, - `HasSelectOrTake extends Prisma.Or>, Prisma.Extends<'take', Prisma.Keys>>`, - `OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.${model.name}GroupByArgs['orderBy'] }: { orderBy?: Prisma.${model.name}GroupByArgs['orderBy'] },`, - `OrderFields extends Prisma.ExcludeUnderscoreKeys>>`, - `ByFields extends Prisma.TupleToUnion`, - `ByValid extends Prisma.Has`, - `HavingFields extends Prisma.GetHavingFields`, - `HavingValid extends Prisma.Has`, - `ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False`, - `InputErrors extends ByEmpty extends Prisma.True - ? \`Error: "by" must not be empty.\` - : HavingValid extends Prisma.False - ? { - [P in HavingFields]: P extends ByFields - ? never - : P extends string - ? \`Error: Field "\${P}" used in "having" needs to be provided in "by".\` - : [ - Error, - 'Field ', - P, - \` in "having" needs to be provided in "by"\`, - ] - }[HavingFields] - : 'take' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` - }[OrderFields] - : 'Error: If you provide "take", you also need to provide "orderBy"' - : 'skip' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` - }[OrderFields] - : 'Error: If you provide "skip", you also need to provide "orderBy"' - : ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` - }[OrderFields]`, - ]; - - const returnType = `{} extends InputErrors ? - Array & - { - [P in ((keyof T) & (keyof Prisma.${model.name}GroupByOutputType))]: P extends '_count' - ? T[P] extends boolean - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType - } - > : InputErrors`; - - generateQueryHook( - sf, - model.name, - 'groupBy', - false, - false, - returnType, - `Prisma.SubsetIntersection & InputErrors`, - typeParameters - ); - } - - // somehow dmmf doesn't contain "count" operation, so we unconditionally add it here - { - generateQueryHook( - sf, - model.name, - 'count', - false, - true, - `T extends { select: any; } ? T['select'] extends true ? number : Prisma.GetScalarType : number` - ); - } -} - -function generateIndex(project: Project, outDir: string, models: DataModel[]) { - const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true }); - sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`)); -} diff --git a/packages/plugins/react/src/generator/swr.ts b/packages/plugins/react/src/generator/swr.ts deleted file mode 100644 index 65ffa06d2..000000000 --- a/packages/plugins/react/src/generator/swr.ts +++ /dev/null @@ -1,501 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { - CrudFailureReason, - PluginOptions, - createProject, - getDataModels, - getPrismaClientImportSpec, - requireOption, - resolvePath, - saveProject, -} from '@zenstackhq/sdk'; -import { DataModel, Model } from '@zenstackhq/sdk/ast'; -import { paramCase } from 'change-case'; -import { lowerCaseFirst } from 'lower-case-first'; -import * as path from 'path'; -import { Project } from 'ts-morph'; - -export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { - let outDir = requireOption(options, 'output'); - outDir = resolvePath(outDir, options); - - const project = createProject(); - const warnings: string[] = []; - const models = getDataModels(model); - - generateIndex(project, outDir, models); - - models.forEach((dataModel) => { - const mapping = dmmf.mappings.modelOperations.find((op) => op.model === dataModel.name); - if (!mapping) { - warnings.push(`Unable to find mapping for model ${dataModel.name}`); - return; - } - generateModelHooks(project, outDir, dataModel, mapping); - }); - - await saveProject(project); - return warnings; -} - -function wrapReadbackErrorCheck(code: string) { - return `try { - ${code} - } catch (err: any) { - if (err.info?.prisma && err.info?.code === 'P2004' && err.info?.reason === '${CrudFailureReason.RESULT_NOT_READABLE}') { - // unable to readback data - return undefined; - } else { - throw err; - } - }`; -} - -function generateModelHooks(project: Project, outDir: string, model: DataModel, mapping: DMMF.ModelMapping) { - const fileName = paramCase(model.name); - const sf = project.createSourceFile(path.join(outDir, `${fileName}.ts`), undefined, { overwrite: true }); - - sf.addStatements('/* eslint-disable */'); - - const prismaImport = getPrismaClientImportSpec(model.$container, outDir); - sf.addImportDeclaration({ - namedImports: ['Prisma', model.name], - isTypeOnly: true, - moduleSpecifier: prismaImport, - }); - sf.addStatements([ - `import { useContext } from 'react';`, - `import { RequestHandlerContext } from '@zenstackhq/react/runtime';`, - `import { type RequestOptions } from '@zenstackhq/react/runtime/swr';`, - `import * as request from '@zenstackhq/react/runtime/swr';`, - ]); - - const useFunc = sf.addFunction({ - name: `use${model.name}`, - isExported: true, - }); - - const prefixesToMutate = ['find', 'aggregate', 'count', 'groupBy']; - const modelRouteName = lowerCaseFirst(model.name); - - useFunc.addStatements([ - 'const { endpoint } = useContext(RequestHandlerContext);', - `const prefixesToMutate = [${prefixesToMutate - .map((prefix) => '`${endpoint}/' + modelRouteName + '/' + prefix + '`') - .join(', ')}];`, - 'const mutate = request.getMutate(prefixesToMutate);', - ]); - - const methods: string[] = []; - - // create is somehow named "createOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.create || (mapping as any).createOne) { - methods.push('create'); - const argsType = `Prisma.${model.name}CreateArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.CheckSelect>`; - useFunc - .addFunction({ - name: 'create', - isAsync: true, - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - ], - }) - .addBody() - .addStatements([ - wrapReadbackErrorCheck( - `return await request.post<${inputType}, ${returnType}>(\`\${endpoint}/${modelRouteName}/create\`, args, mutate);` - ), - ]); - } - - // createMany - if (mapping.createMany) { - methods.push('createMany'); - const argsType = `Prisma.${model.name}CreateManyArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.BatchPayload`; - useFunc - .addFunction({ - name: 'createMany', - isAsync: true, - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - ], - }) - .addBody() - .addStatements([ - `return await request.post<${inputType}, ${returnType}>(\`\${endpoint}/${modelRouteName}/createMany\`, args, mutate);`, - ]); - } - - // findMany - if (mapping.findMany) { - methods.push('findMany'); - const argsType = `Prisma.${model.name}FindManyArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Array>`; - useFunc - .addFunction({ - name: 'findMany', - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args?', - type: inputType, - }, - { - name: 'options?', - type: `RequestOptions<${returnType}>`, - }, - ], - }) - .addBody() - .addStatements([ - `return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/findMany\`, args, options);`, - ]); - } - - // findUnique - if (mapping.findUnique) { - methods.push('findUnique'); - const argsType = `Prisma.${model.name}FindUniqueArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.${model.name}GetPayload`; - useFunc - .addFunction({ - name: 'findUnique', - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - { - name: 'options?', - type: `RequestOptions<${returnType}>`, - }, - ], - }) - .addBody() - .addStatements([ - `return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/findUnique\`, args, options);`, - ]); - } - - // findFirst - if (mapping.findFirst) { - methods.push('findFirst'); - const argsType = `Prisma.${model.name}FindFirstArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.${model.name}GetPayload`; - useFunc - .addFunction({ - name: 'findFirst', - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - { - name: 'options?', - type: `RequestOptions<${returnType}>`, - }, - ], - }) - .addBody() - .addStatements([ - `return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/findFirst\`, args, options);`, - ]); - } - - // update - // update is somehow named "updateOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.update || (mapping as any).updateOne) { - methods.push('update'); - const argsType = `Prisma.${model.name}UpdateArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.${model.name}GetPayload`; - useFunc - .addFunction({ - name: 'update', - isAsync: true, - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - ], - }) - .addBody() - .addStatements([ - wrapReadbackErrorCheck( - `return await request.put<${inputType}, ${returnType}>(\`\${endpoint}/${modelRouteName}/update\`, args, mutate);` - ), - ]); - } - - // updateMany - if (mapping.updateMany) { - methods.push('updateMany'); - const argsType = `Prisma.${model.name}UpdateManyArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.BatchPayload`; - useFunc - .addFunction({ - name: 'updateMany', - isAsync: true, - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - ], - }) - .addBody() - .addStatements([ - `return await request.put<${inputType}, ${returnType}>(\`\${endpoint}/${modelRouteName}/updateMany\`, args, mutate);`, - ]); - } - - // upsert - // upsert is somehow named "upsertOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.upsert || (mapping as any).upsertOne) { - methods.push('upsert'); - const argsType = `Prisma.${model.name}UpsertArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.${model.name}GetPayload`; - useFunc - .addFunction({ - name: 'upsert', - isAsync: true, - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - ], - }) - .addBody() - .addStatements([ - wrapReadbackErrorCheck( - `return await request.post<${inputType}, ${returnType}>(\`\${endpoint}/${modelRouteName}/upsert\`, args, mutate);` - ), - ]); - } - - // del - // delete is somehow named "deleteOne" in the DMMF - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (mapping.delete || (mapping as any).deleteOne) { - methods.push('del'); - const argsType = `Prisma.${model.name}DeleteArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.${model.name}GetPayload`; - useFunc - .addFunction({ - name: 'del', - isAsync: true, - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - ], - }) - .addBody() - .addStatements([ - wrapReadbackErrorCheck( - `return await request.del<${returnType}>(\`\${endpoint}/${modelRouteName}/delete\`, args, mutate);` - ), - ]); - } - - // deleteMany - if (mapping.deleteMany) { - methods.push('deleteMany'); - const argsType = `Prisma.${model.name}DeleteManyArgs`; - const inputType = `Prisma.SelectSubset`; - const returnType = `Prisma.BatchPayload`; - useFunc - .addFunction({ - name: 'deleteMany', - isAsync: true, - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args?', - type: inputType, - }, - ], - }) - .addBody() - .addStatements([ - `return await request.del<${returnType}>(\`\${endpoint}/${modelRouteName}/deleteMany\`, args, mutate);`, - ]); - } - - // aggregate - if (mapping.aggregate) { - methods.push('aggregate'); - const argsType = `Prisma.${model.name}AggregateArgs`; - const inputType = `Prisma.Subset`; - const returnType = `Prisma.Get${model.name}AggregateType`; - useFunc - .addFunction({ - name: 'aggregate', - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - { - name: 'options?', - type: `RequestOptions<${returnType}>`, - }, - ], - }) - .addBody() - .addStatements([ - `return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/aggregate\`, args, options);`, - ]); - } - - // groupBy - if (mapping.groupBy) { - methods.push('groupBy'); - const returnType = `{} extends InputErrors ? - Array & - { - [P in ((keyof T) & (keyof Prisma.${model.name}GroupByOutputType))]: P extends '_count' - ? T[P] extends boolean - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType - } - > : InputErrors`; - useFunc - .addFunction({ - name: 'groupBy', - typeParameters: [ - `T extends Prisma.${model.name}GroupByArgs`, - `HasSelectOrTake extends Prisma.Or>, Prisma.Extends<'take', Prisma.Keys>>`, - `OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.${model.name}GroupByArgs['orderBy'] }: { orderBy?: Prisma.${model.name}GroupByArgs['orderBy'] },`, - `OrderFields extends Prisma.ExcludeUnderscoreKeys>>`, - `ByFields extends Prisma.TupleToUnion`, - `ByValid extends Prisma.Has`, - `HavingFields extends Prisma.GetHavingFields`, - `HavingValid extends Prisma.Has`, - `ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False`, - `InputErrors extends ByEmpty extends Prisma.True - ? \`Error: "by" must not be empty.\` - : HavingValid extends Prisma.False - ? { - [P in HavingFields]: P extends ByFields - ? never - : P extends string - ? \`Error: Field "\${P}" used in "having" needs to be provided in "by".\` - : [ - Error, - 'Field ', - P, - \` in "having" needs to be provided in "by"\`, - ] - }[HavingFields] - : 'take' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` - }[OrderFields] - : 'Error: If you provide "take", you also need to provide "orderBy"' - : 'skip' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` - }[OrderFields] - : 'Error: If you provide "skip", you also need to provide "orderBy"' - : ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` - }[OrderFields]`, - ], - parameters: [ - { - name: 'args', - type: `Prisma.SubsetIntersection & InputErrors`, - }, - { - name: 'options?', - type: `RequestOptions<${returnType}>`, - }, - ], - }) - .addBody() - .addStatements([ - `return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/groupBy\`, args, options);`, - ]); - } - - // somehow dmmf doesn't contain "count" operation, so we unconditionally add it here - { - methods.push('count'); - const argsType = `Prisma.${model.name}CountArgs`; - const inputType = `Prisma.Subset`; - const returnType = `T extends { select: any; } ? T['select'] extends true ? number : Prisma.GetScalarType : number`; - useFunc - .addFunction({ - name: 'count', - typeParameters: [`T extends ${argsType}`], - parameters: [ - { - name: 'args', - type: inputType, - }, - { - name: 'options?', - type: `RequestOptions<${returnType}>`, - }, - ], - }) - .addBody() - .addStatements([ - `return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/count\`, args, options);`, - ]); - } - - useFunc.addStatements([`return { ${methods.join(', ')} };`]); -} - -function generateIndex(project: Project, outDir: string, models: DataModel[]) { - const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true }); - sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`)); -} diff --git a/packages/plugins/react/src/index.ts b/packages/plugins/react/src/index.ts deleted file mode 100644 index 3baa559a9..000000000 --- a/packages/plugins/react/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { DMMF } from '@prisma/generator-helper'; -import type { PluginOptions } from '@zenstackhq/sdk'; -import type { Model } from '@zenstackhq/sdk/ast'; -import { generate } from './generator'; - -export const name = 'React'; - -export default async function run(model: Model, options: PluginOptions, dmmf: DMMF.Document) { - return generate(model, options, dmmf); -} diff --git a/packages/plugins/react/src/runtime/index.ts b/packages/plugins/react/src/runtime/index.ts deleted file mode 100644 index e1ff8d2d0..000000000 --- a/packages/plugins/react/src/runtime/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createContext } from 'react'; - -/** - * Context type for configuring react hooks. - */ -export type RequestHandlerContext = { - endpoint: string; -}; - -/** - * Context for configuring react hooks. - */ -export const RequestHandlerContext = createContext({ - endpoint: '/api/model', -}); - -/** - * Context provider. - */ -export const Provider = RequestHandlerContext.Provider; diff --git a/packages/plugins/react/src/runtime/react-query.ts b/packages/plugins/react/src/runtime/react-query.ts deleted file mode 100644 index 6fd04f329..000000000 --- a/packages/plugins/react/src/runtime/react-query.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { - useMutation, - useQuery, - useQueryClient, - type MutateFunction, - type QueryClient, - type UseMutationOptions, - type UseQueryOptions, -} from '@tanstack/react-query'; -import { marshal, registerSerializers } from '../serialization-utils'; -import { fetcher, makeUrl } from './utils'; - -// register superjson custom serializers -registerSerializers(); - -const QUERY_KEY_PREFIX = 'zenstack:'; - -/** - * Creates a react-query query. - * - * @param model The name of the model under query. - * @param url The request URL. - * @param args The request args object, which will be superjson-stringified and appended as "?q=" parameter - * @param options The react-query options object - * @returns useQuery hook - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function query(model: string, url: string, args?: unknown, options?: UseQueryOptions) { - const reqUrl = makeUrl(url, args); - return useQuery({ - queryKey: [QUERY_KEY_PREFIX + model, url, args], - queryFn: () => fetcher(reqUrl), - ...options, - }); -} - -/** - * Creates a POST mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function postMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - invalidateQueries = true -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher(url, { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }); - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a PUT mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function putMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - invalidateQueries = true -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher(url, { - method: 'PUT', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }); - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a DELETE mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function deleteMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - invalidateQueries = true -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher(makeUrl(url, data), { - method: 'DELETE', - }); - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -function mergeOptions( - model: string, - options: Omit, 'mutationFn'> | undefined, - invalidateQueries: boolean, - mutationFn: MutateFunction, - queryClient: QueryClient -): UseMutationOptions { - const result = { ...options, mutationFn }; - if (options?.onSuccess || invalidateQueries) { - result.onSuccess = (...args) => { - if (invalidateQueries) { - queryClient.invalidateQueries([QUERY_KEY_PREFIX + model]); - } - return options?.onSuccess?.(...args); - }; - } - return result; -} diff --git a/packages/plugins/react/src/runtime/swr.ts b/packages/plugins/react/src/runtime/swr.ts deleted file mode 100644 index ce84702ec..000000000 --- a/packages/plugins/react/src/runtime/swr.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { MutatorCallback, MutatorOptions, SWRResponse } from 'swr'; -import useSWR, { useSWRConfig } from 'swr'; -import { marshal, registerSerializers } from '../serialization-utils'; -import { fetcher, makeUrl } from './utils'; - -/** - * Client request options - */ -export type RequestOptions = { - // disable data fetching - disabled?: boolean; - initialData?: T; -}; - -// register superjson custom serializers -registerSerializers(); - -/** - * Makes a GET request with SWR. - * - * @param url The request URL. - * @param args The request args object, which will be superjson-stringified and appended as "?q=" parameter - * @returns SWR response - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function get( - url: string | null, - args?: unknown, - options?: RequestOptions -): SWRResponse { - const reqUrl = options?.disabled ? null : url ? makeUrl(url, args) : null; - return useSWR(reqUrl, fetcher, { - fallbackData: options?.initialData, - }); -} - -/** - * Makes a POST request. - * - * @param url The request URL. - * @param data The request data. - * @param mutate Mutator for invalidating cache. - */ -export async function post(url: string, data: Data, mutate: Mutator): Promise { - const r: Result = await fetcher(url, { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }); - mutate(); - return r; -} - -/** - * Makes a PUT request. - * - * @param url The request URL. - * @param data The request data. - * @param mutate Mutator for invalidating cache. - */ -export async function put(url: string, data: Data, mutate: Mutator): Promise { - const r: Result = await fetcher(url, { - method: 'PUT', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }); - mutate(); - return r; -} - -/** - * Makes a DELETE request. - * - * @param url The request URL. - * @param args The request args object, which will be superjson-stringified and appended as "?q=" parameter - * @param mutate Mutator for invalidating cache. - */ -export async function del(url: string, args: unknown, mutate: Mutator): Promise { - const reqUrl = makeUrl(url, args); - const r: Result = await fetcher(reqUrl, { - method: 'DELETE', - }); - const path = url.split('/'); - path.pop(); - mutate(); - return r; -} - -type Mutator = ( - data?: unknown | Promise | MutatorCallback, - opts?: boolean | MutatorOptions -) => Promise; - -export function getMutate(prefixes: string[]): Mutator { - // https://swr.vercel.app/docs/advanced/cache#mutate-multiple-keys-from-regex - const { cache, mutate } = useSWRConfig(); - return (data?: unknown | Promise | MutatorCallback, opts?: boolean | MutatorOptions) => { - if (!(cache instanceof Map)) { - throw new Error('mutate requires the cache provider to be a Map instance'); - } - - const keys = Array.from(cache.keys()).filter( - (k) => typeof k === 'string' && prefixes.some((prefix) => k.startsWith(prefix)) - ) as string[]; - const mutations = keys.map((key) => mutate(key, data, opts)); - return Promise.all(mutations); - }; -} diff --git a/packages/plugins/react/src/runtime/utils.ts b/packages/plugins/react/src/runtime/utils.ts deleted file mode 100644 index ec92a0e2b..000000000 --- a/packages/plugins/react/src/runtime/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { marshal, unmarshal } from '../serialization-utils'; - -export function makeUrl(url: string, args: unknown) { - return args ? url + `?q=${encodeURIComponent(marshal(args))}` : url; -} - -export async function fetcher(url: string, options?: RequestInit) { - const res = await fetch(url, options); - if (!res.ok) { - const error: Error & { info?: unknown; status?: number } = new Error( - 'An error occurred while fetching the data.' - ); - error.info = await res.json(); - error.status = res.status; - throw error; - } - - const textResult = await res.text(); - try { - return unmarshal(textResult) as R; - } catch (err) { - console.error(`Unable to deserialize data:`, textResult); - throw err; - } -} diff --git a/packages/plugins/react/src/serialization-utils.ts b/packages/plugins/react/src/serialization-utils.ts deleted file mode 100644 index 2889fe851..000000000 --- a/packages/plugins/react/src/serialization-utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import Decimal from 'decimal.js'; -import superjson from 'superjson'; - -export function registerSerializers() { - superjson.registerCustom( - { - isApplicable: (v): v is Buffer => Buffer.isBuffer(v), - serialize: (v) => JSON.stringify(v.toJSON().data), - deserialize: (v) => Buffer.from(JSON.parse(v)), - }, - 'Buffer' - ); - superjson.registerCustom( - { - isApplicable: (v): v is Decimal => Decimal.isDecimal(v), - serialize: (v) => v.toJSON(), - deserialize: (v) => new Decimal(v), - }, - 'decimal.js' - ); -} - -export function marshal(value: unknown) { - return superjson.stringify(value); -} - -export function unmarshal(value: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return superjson.parse(value); -} diff --git a/packages/plugins/react/tests/react-hooks.test.ts b/packages/plugins/react/tests/react-hooks.test.ts deleted file mode 100644 index e27c454a1..000000000 --- a/packages/plugins/react/tests/react-hooks.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/// - -import { loadSchema } from '@zenstackhq/testtools'; - -describe('React Hooks Plugin Tests', () => { - let origDir: string; - - beforeAll(() => { - origDir = process.cwd(); - }); - - afterEach(() => { - process.chdir(origDir); - }); - - const sharedModel = ` -model User { - id String @id - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - email String @unique - role String @default('USER') - posts Post[] -} - -model Post { - id String @id - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - title String - author User? @relation(fields: [authorId], references: [id]) - authorId String? - published Boolean @default(false) - viewCount Int @default(0) -} - -model Foo { - id String @id - @@ignore -} - `; - - it('swr generator', async () => { - await loadSchema( - ` -plugin react { - provider = '${process.cwd()}/dist' - output = '$projectRoot/hooks' -} - -${sharedModel} - `, - true, - false, - [`${origDir}/dist`, 'react', '@types/react', 'swr'], - true - ); - }); - - it('react-query generator', async () => { - await loadSchema( - ` -plugin react { - provider = '${process.cwd()}/dist' - output = '$projectRoot/hooks' - fetcher = 'react-query' -} - -${sharedModel} - `, - true, - false, - [`${origDir}/dist`, 'react', '@types/react', '@tanstack/react-query'], - true - ); - }); -}); diff --git a/packages/plugins/react/tsconfig.json b/packages/plugins/react/tsconfig.json deleted file mode 100644 index e2c5bd0f3..000000000 --- a/packages/plugins/react/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES6", - "module": "CommonJS", - "lib": ["ESNext", "DOM"], - "sourceMap": true, - "outDir": "dist", - "strict": true, - "noUnusedLocals": true, - "noImplicitReturns": true, - "moduleResolution": "node", - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "resolveJsonModule": true, - "strictPropertyInitialization": false, - "paths": {} - }, - "include": ["src/**/*.ts"] -} diff --git a/packages/schema/tests/schema/formatter.test.ts b/packages/schema/tests/schema/formatter.test.ts index 413fbf369..c0f296216 100644 --- a/packages/schema/tests/schema/formatter.test.ts +++ b/packages/schema/tests/schema/formatter.test.ts @@ -11,7 +11,7 @@ describe('ZModelFormatter', () => { test.skip('declaration formatting', async () => { await formatting({ before: `datasource db { provider = 'postgresql' url = env('DATABASE_URL')} generator js {provider = 'prisma-client-js'} - plugin reactHooks {provider = '@zenstackhq/react'output = 'lib/hooks'} + plugin swrHooks {provider = '@zenstackhq/swr'output = 'lib/hooks'} model User {id:id String @id name String? } enum Role {ADMIN USER}`, after: `datasource db { @@ -21,8 +21,8 @@ describe('ZModelFormatter', () => { generator js { provider = 'prisma-client-js' } -plugin reactHooks { - provider = '@zenstackhq/react' +plugin swrHooks { + provider = '@zenstackhq/swr' output = 'lib/hooks' } model User { diff --git a/packages/schema/tests/schema/todo.zmodel b/packages/schema/tests/schema/todo.zmodel index d08b3049b..2894c486c 100644 --- a/packages/schema/tests/schema/todo.zmodel +++ b/packages/schema/tests/schema/todo.zmodel @@ -14,8 +14,8 @@ generator js { provider = 'prisma-client-js' } -plugin reactHooks { - provider = '@zenstackhq/react' +plugin hooks { + provider = '@zenstackhq/swr' output = 'lib/hooks' } @@ -59,7 +59,7 @@ model SpaceUser { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - space Space @relation(fields:[spaceId], references: [id], onDelete: Cascade) + space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade) spaceId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId String diff --git a/packages/testtools/src/package.template.json b/packages/testtools/src/package.template.json index ca8aa031e..fc936c9a2 100644 --- a/packages/testtools/src/package.template.json +++ b/packages/testtools/src/package.template.json @@ -9,7 +9,7 @@ "dependencies": { "@prisma/client": "^4.0.0", "@zenstackhq/runtime": "file:/packages/runtime/dist", - "@zenstackhq/react": "file:/packages/plugins/react/dist", + "@zenstackhq/swr": "file:/packages/plugins/swr/dist", "@zenstackhq/trpc": "file:/packages/plugins/trpc/dist", "@zenstackhq/openapi": "file:/packages/plugins/openapi/dist", "prisma": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a170769d1..478d78972 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -174,80 +174,6 @@ importers: version: link:../../schema/dist publishDirectory: dist - packages/plugins/react: - dependencies: - '@prisma/generator-helper': - specifier: ^4.7.1 - version: 4.7.1 - '@zenstackhq/sdk': - specifier: workspace:* - version: link:../../sdk/dist - change-case: - specifier: ^4.1.2 - version: 4.1.2 - decimal.js: - specifier: ^10.4.2 - version: 10.4.2 - lower-case-first: - specifier: ^2.0.2 - version: 2.0.2 - superjson: - specifier: ^1.11.0 - version: 1.12.1 - ts-morph: - specifier: ^16.0.0 - version: 16.0.0 - upper-case-first: - specifier: ^2.0.2 - version: 2.0.2 - devDependencies: - '@tanstack/react-query': - specifier: ^4.28.0 - version: 4.28.0(react-dom@18.2.0)(react@18.2.0) - '@types/jest': - specifier: ^29.5.0 - version: 29.5.0 - '@types/lower-case-first': - specifier: ^1.0.1 - version: 1.0.1 - '@types/react': - specifier: ^18.0.26 - version: 18.0.26 - '@types/tmp': - specifier: ^0.2.3 - version: 0.2.3 - '@types/upper-case-first': - specifier: ^1.1.2 - version: 1.1.2 - '@zenstackhq/testtools': - specifier: workspace:* - version: link:../../testtools/dist - copyfiles: - specifier: ^2.4.1 - version: 2.4.1 - jest: - specifier: ^29.5.0 - version: 29.5.0(@types/node@14.18.29)(ts-node@10.9.1) - react: - specifier: ^17.0.2 || ^18 - version: 18.2.0 - react-dom: - specifier: ^17.0.2 || ^18 - version: 18.2.0(react@18.2.0) - rimraf: - specifier: ^3.0.2 - version: 3.0.2 - swr: - specifier: ^2.0.3 - version: 2.0.3(react@18.2.0) - ts-jest: - specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.20.5)(jest@29.5.0)(typescript@4.9.4) - typescript: - specifier: ^4.9.4 - version: 4.9.4 - publishDirectory: dist - packages/plugins/swr: dependencies: '@prisma/generator-helper': @@ -948,12 +874,12 @@ importers: '@zenstackhq/next': specifier: workspace:* version: link:../../packages/next/dist - '@zenstackhq/react': - specifier: workspace:* - version: link:../../packages/plugins/react/dist '@zenstackhq/runtime': specifier: workspace:* version: link:../../packages/runtime/dist + '@zenstackhq/swr': + specifier: workspace:* + version: link:../../packages/plugins/swr/dist '@zenstackhq/trpc': specifier: workspace:* version: link:../../packages/plugins/trpc/dist @@ -2838,32 +2764,10 @@ packages: tslib: 2.4.1 dev: true - /@tanstack/query-core@4.27.0: - resolution: {integrity: sha512-sm+QncWaPmM73IPwFlmWSKPqjdTXZeFf/7aEmWh00z7yl2FjqophPt0dE1EHW9P1giMC5rMviv7OUbSDmWzXXA==} - dev: true - /@tanstack/query-core@4.29.7: resolution: {integrity: sha512-GXG4b5hV2Loir+h2G+RXhJdoZhJLnrBWsuLB2r0qBRyhWuXq9w/dWxzvpP89H0UARlH6Mr9DiVj4SMtpkF/aUA==} dev: true - /@tanstack/react-query@4.28.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-8cGBV5300RHlvYdS4ea+G1JcZIt5CIuprXYFnsWggkmGoC0b5JaqG0fIX3qwDL9PTNkKvG76NGThIWbpXivMrQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@tanstack/query-core': 4.27.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - use-sync-external-store: 1.2.0(react@18.2.0) - dev: true - /@tanstack/react-query@4.29.7(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ijBWEzAIo09fB1yd22slRZzprrZ5zMdWYzBnCg5qiXuFbH78uGN1qtGz8+Ed4MuhaPaYSD+hykn+QEKtQviEtg==} peerDependencies: diff --git a/tests/integration/package.json b/tests/integration/package.json index 8e8d5ead3..9136099ed 100644 --- a/tests/integration/package.json +++ b/tests/integration/package.json @@ -18,7 +18,7 @@ "@types/tmp": "^0.2.3", "@types/uuid": "^8.3.4", "@zenstackhq/next": "workspace:*", - "@zenstackhq/react": "workspace:*", + "@zenstackhq/swr": "workspace:*", "@zenstackhq/runtime": "workspace:*", "@zenstackhq/trpc": "workspace:*", "eslint": "^8.30.0", diff --git a/tests/integration/tests/nextjs/generation.test.ts b/tests/integration/tests/nextjs/generation.test.ts index 81ea407ae..4ba155ff0 100644 --- a/tests/integration/tests/nextjs/generation.test.ts +++ b/tests/integration/tests/nextjs/generation.test.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import fse from 'fs-extra'; import path from 'path'; -describe('React Hooks Generation Tests', () => { +describe('SWR Hooks Generation Tests', () => { let origDir: string; beforeAll(() => { diff --git a/tests/integration/tests/nextjs/test-project/package-lock.json b/tests/integration/tests/nextjs/test-project/package-lock.json index 304de992c..5300d2e62 100644 --- a/tests/integration/tests/nextjs/test-project/package-lock.json +++ b/tests/integration/tests/nextjs/test-project/package-lock.json @@ -8,15 +8,22 @@ "name": "test-project", "version": "0.1.0", "dependencies": { + "@prisma/client": "^4.7.0", "@types/node": "18.11.18", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", + "@zenstackhq/runtime": "../../../../../../packages/runtime/dist", "next": "13.1.4", "react": "18.2.0", "react-dom": "18.2.0", + "swr": "^2.2.0", "typescript": "4.9.4" + }, + "devDependencies": { + "prisma": "^4.7.0" } }, + "../../../../../../packages/runtime/dist": {}, "node_modules/@next/env": { "version": "13.1.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.4.tgz", @@ -217,6 +224,38 @@ "node": ">= 10" } }, + "node_modules/@prisma/client": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.1.tgz", + "integrity": "sha512-CoDHu7Bt+NuDo40ijoeHP79EHtECsPBTy3yte5Yo3op8TqXt/kV0OT5OrsWewKvQGKFMHhYQ+ePed3zzjYdGAw==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.1.tgz", + "integrity": "sha512-gpZG0kGGxfemgvK/LghHdBIz+crHkZjzszja94xp4oytpsXrgt/Ice82MvPsWMleVIniKuARrowtsIsim0PFJQ==", + "devOptional": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c.tgz", + "integrity": "sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==" + }, "node_modules/@swc/helpers": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", @@ -258,6 +297,10 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "node_modules/@zenstackhq/runtime": { + "resolved": "../../../../../../packages/runtime/dist", + "link": true + }, "node_modules/caniuse-lite": { "version": "1.0.30001446", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001446.tgz", @@ -389,6 +432,23 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prisma": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz", + "integrity": "sha512-C2Xm7yxHxjFjjscBEW4tmoraPHH/Vyu/A0XABdbaFtoiOZARsxvOM7rwc2iZ0qVxbh0bGBGBWZUSXO/52/nHBQ==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "4.16.1" + }, + "bin": { + "prisma": "build/index.js", + "prisma2": "build/index.js" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -450,6 +510,17 @@ } } }, + "node_modules/swr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", + "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -466,6 +537,14 @@ "engines": { "node": ">=4.2.0" } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } } }, "dependencies": { @@ -552,6 +631,25 @@ "integrity": "sha512-bygNjmnq+F9NqJXh7OfhJgqu6LGU29GNKQYVyZkxY/h5K0WWUvAE/VL+TdyMwbvQr9KByx5XLwORwetLxXCo4g==", "optional": true }, + "@prisma/client": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.1.tgz", + "integrity": "sha512-CoDHu7Bt+NuDo40ijoeHP79EHtECsPBTy3yte5Yo3op8TqXt/kV0OT5OrsWewKvQGKFMHhYQ+ePed3zzjYdGAw==", + "requires": { + "@prisma/engines-version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c" + } + }, + "@prisma/engines": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.1.tgz", + "integrity": "sha512-gpZG0kGGxfemgvK/LghHdBIz+crHkZjzszja94xp4oytpsXrgt/Ice82MvPsWMleVIniKuARrowtsIsim0PFJQ==", + "devOptional": true + }, + "@prisma/engines-version": { + "version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c.tgz", + "integrity": "sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==" + }, "@swc/helpers": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", @@ -593,6 +691,9 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@zenstackhq/runtime": { + "version": "file:../../../../../../packages/runtime/dist" + }, "caniuse-lite": { "version": "1.0.30001446", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001446.tgz", @@ -666,6 +767,15 @@ "source-map-js": "^1.0.2" } }, + "prisma": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz", + "integrity": "sha512-C2Xm7yxHxjFjjscBEW4tmoraPHH/Vyu/A0XABdbaFtoiOZARsxvOM7rwc2iZ0qVxbh0bGBGBWZUSXO/52/nHBQ==", + "devOptional": true, + "requires": { + "@prisma/engines": "4.16.1" + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -704,6 +814,14 @@ "client-only": "0.0.1" } }, + "swr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", + "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "requires": { + "use-sync-external-store": "^1.2.0" + } + }, "tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -713,6 +831,12 @@ "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==" + }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} } } } diff --git a/tests/integration/tests/nextjs/test-project/package.json b/tests/integration/tests/nextjs/test-project/package.json index 1a010be9d..b0f618b2f 100644 --- a/tests/integration/tests/nextjs/test-project/package.json +++ b/tests/integration/tests/nextjs/test-project/package.json @@ -9,15 +9,16 @@ "lint": "next lint" }, "dependencies": { + "@prisma/client": "^4.7.0", "@types/node": "18.11.18", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", + "@zenstackhq/runtime": "../../../../../../packages/runtime/dist", "next": "13.1.4", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "4.9.4", - "@prisma/client": "^4.7.0", - "@zenstackhq/runtime": "../../../../../../packages/runtime/dist" + "swr": "^2.2.0", + "typescript": "4.9.4" }, "devDependencies": { "prisma": "^4.7.0" diff --git a/tests/integration/tests/nextjs/test-project/pages/index.tsx b/tests/integration/tests/nextjs/test-project/pages/index.tsx index d50523aa2..97eb48ff8 100644 --- a/tests/integration/tests/nextjs/test-project/pages/index.tsx +++ b/tests/integration/tests/nextjs/test-project/pages/index.tsx @@ -1,8 +1,7 @@ -import { usePost } from '../lib/hooks'; +import { useFindManyPost } from '../lib/hooks'; export default function Home() { - const { findMany } = usePost(); - const { data: posts } = findMany(); + const { data: posts } = useFindManyPost(); return (
{posts?.map((post) => ( diff --git a/tests/integration/tests/nextjs/test-project/postgres.zmodel b/tests/integration/tests/nextjs/test-project/postgres.zmodel index d0de3e795..2f50ae204 100644 --- a/tests/integration/tests/nextjs/test-project/postgres.zmodel +++ b/tests/integration/tests/nextjs/test-project/postgres.zmodel @@ -17,8 +17,8 @@ plugin policy { output = '.zenstack' } -plugin react { - provider = '@zenstackhq/react' +plugin swr { + provider = '@zenstackhq/swr' output = 'lib/hooks' } diff --git a/tests/integration/tests/nextjs/test-project/sqlite.zmodel b/tests/integration/tests/nextjs/test-project/sqlite.zmodel index d69c28c89..f36be48cb 100644 --- a/tests/integration/tests/nextjs/test-project/sqlite.zmodel +++ b/tests/integration/tests/nextjs/test-project/sqlite.zmodel @@ -17,8 +17,8 @@ plugin policy { output = '.zenstack' } -plugin react { - provider = '@zenstackhq/react' +plugin swr { + provider = '@zenstackhq/swr' output = 'lib/hooks' }