@zenstackhq/redwood 0.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -2
- package/bin/cli +3 -0
- package/dist/cli-passthrough.d.ts +5 -0
- package/dist/cli-passthrough.js +76 -0
- package/dist/cli-passthrough.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.js +229 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/graphql.d.ts +18 -0
- package/dist/graphql.js +48 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/setup-package.d.ts +1 -0
- package/dist/setup-package.js +28 -0
- package/dist/setup-package.js.map +1 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +35 -0
- package/dist/utils.js.map +1 -0
- package/package.json +24 -16
- package/src/cli-passthrough.ts +49 -0
- package/src/commands/setup.ts +262 -0
- package/src/graphql.ts +61 -0
- package/src/index.ts +11 -0
- package/src/setup-package.ts +11 -0
- package/src/utils.ts +20 -0
- package/tsconfig.json +8 -0
- package/index.d.ts +0 -6
- package/index.js +0 -15
- package/index.js.map +0 -1
- package/setup.d.ts +0 -0
- package/setup.js +0 -3
- package/setup.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,97 @@
|
|
|
1
1
|
# ZenStack RedwoodJS Integration
|
|
2
2
|
|
|
3
|
-
This package
|
|
3
|
+
This package provides the CLI and runtime APIs for integrating [ZenStack](https://zenstack.dev) into a [RedwoodJS](https://redwoodjs.com/) project. You can use ZenStack as a drop-in replacement to Prisma and define flexible access control policies declaratively inside the database schema. It's especially useful for building multi-tenant applications which tend to have complex authorization requirements beyond RBAC.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
ZenStack is a full-stack toolkit built above Prisma ORM. It extends Prisma at the schema and the runtime level for adding the following capabilities:
|
|
6
|
+
|
|
7
|
+
- Flexible access control
|
|
8
|
+
- Data validation rules
|
|
9
|
+
- Multi-file schemas
|
|
10
|
+
- Custom attributes and functions in schemas
|
|
11
|
+
|
|
12
|
+
You can find a more detailed integration guide [here](https://zenstack.dev/docs/guides/redwood).
|
|
13
|
+
|
|
14
|
+
### Setting up
|
|
15
|
+
|
|
16
|
+
Run the following package setup command:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
yarn rw setup package @zenstackhq/redwood
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The setup command will:
|
|
23
|
+
|
|
24
|
+
1. Update "redwood.toml" to allow ZenStack CLI plugin.
|
|
25
|
+
1. Install ZenStack dependencies.
|
|
26
|
+
1. Copy your Prisma schema file "api/db/schema.prisma" to "api/db/schema.zmodel".
|
|
27
|
+
1. Add a "zenstack" section into "api/package.json" to specify the location 1f both the "schema.prisma" and "schema.zmodel" files.
|
|
28
|
+
1. Install a GraphQLYoga plugin in "api/src/functions/graphql.[ts|js]".
|
|
29
|
+
1. Eject service templates and modify the templates to use `context.db` (ZenStack-enhanced `PrismaClient`) instead of `db` for data access.
|
|
30
|
+
|
|
31
|
+
### Modeling data and access policies
|
|
32
|
+
|
|
33
|
+
ZenStack's ZModel language is a superset of Prisma schema language. You should use it to define both the data schema and access policies. [The Complete Guide](https://zenstack.dev/docs/the-complete-guide/part1/) of ZenStack is the best way to learn how to author ZModel schemas.
|
|
34
|
+
|
|
35
|
+
You should run the following command after updating "schema.zmodel":
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
yarn rw @zenstackhq generate
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The command does the following things:
|
|
42
|
+
|
|
43
|
+
1. Regenerate "schema.prisma"
|
|
44
|
+
2. Run `prisma generate` to regenerate PrismaClient
|
|
45
|
+
3. Generates supporting JS modules for enforcing access policies at runtime
|
|
46
|
+
|
|
47
|
+
<!-- You can also use the
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
yarn rw @zenstackhq sample
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
command to browse a list of sample schemas and create from them. -->
|
|
54
|
+
|
|
55
|
+
### Development workflow
|
|
56
|
+
|
|
57
|
+
The workflow of using ZenStack is very similar to using Prisma in RedwoodJS projects. The two main differences are:
|
|
58
|
+
|
|
59
|
+
1. Generation
|
|
60
|
+
|
|
61
|
+
You should run `yarn rw @zenstackhq generate` in place of `yarn rw prisma generate`. The ZenStack's generate command internally regenerates the Prisma schema from the ZModel schema, runs `prisma generate` automatically, and also generates other modules for supporting access policy enforcement at the runtime.
|
|
62
|
+
|
|
63
|
+
2. Database access in services
|
|
64
|
+
|
|
65
|
+
In your service code, you should use `context.db` instead of `db` for accessing the database. The `context.db` is an enhanced Prisma client that enforces access policies.
|
|
66
|
+
|
|
67
|
+
The "setup" command prepared a customized service code template. When you run `yarn rw g service`, the generated code will already use `context.db`.
|
|
68
|
+
|
|
69
|
+
Other Prisma-related workflows like generation migration or pushing schema to the database stay unchanged.
|
|
70
|
+
|
|
71
|
+
### Deployment
|
|
72
|
+
|
|
73
|
+
You should run the "generate" command in your deployment script before `yarn rw deploy`. For example, to deploy to Vercel, the command can be:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
yarn rw @zenstackhq generate && yarn rw deploy vercel
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Using the `@zenstackhq` CLI plugin
|
|
80
|
+
|
|
81
|
+
The `@zenstackhq/redwood` package registers a set of custom commands to the RedwoodJS CLI under the `@zenstackhq` namespace. You can run it with:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
yarn rw @zenstackhq <cmd> [options]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The plugin is a simple wrapper of the standard `zenstack` CLI, similar to how RedwoodJS wraps the standard `prisma` CLI. It's equivalent to running `npx zenstack ...` inside the "api" directory.
|
|
88
|
+
|
|
89
|
+
See the [CLI references](https://zenstack.dev/docs/reference/cli) for the full list of commands.
|
|
90
|
+
|
|
91
|
+
### Sample application
|
|
92
|
+
|
|
93
|
+
You can find a complete multi-tenant Todo application built with RedwoodJS and ZenStack at: [https://github.com/zenstackhq/sample-todo-redwood](https://github.com/zenstackhq/sample-todo-redwood).
|
|
94
|
+
|
|
95
|
+
### Getting help
|
|
96
|
+
|
|
97
|
+
The best way to get help and updates about ZenStack is by joining our [Discord server](https://discord.gg/Ykhr738dUe).
|
package/bin/cli
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.makePassthroughCommand = void 0;
|
|
27
|
+
const cli_helpers_1 = require("@redwoodjs/cli-helpers");
|
|
28
|
+
const colors_1 = __importDefault(require("colors"));
|
|
29
|
+
const execa_1 = __importDefault(require("execa"));
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
function runCommand(command, options) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
const args = ['zenstack', command];
|
|
34
|
+
for (const [name, value] of Object.entries(options)) {
|
|
35
|
+
args.push(name.length > 1 ? `--${name}` : `-${name}`);
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
// Make sure options that take multiple quoted words
|
|
38
|
+
// are passed to zenstack with quotes.
|
|
39
|
+
value.split(' ').length > 1 ? args.push(`"${value}"`) : args.push(value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(colors_1.default.green('Running ZenStack CLI...'));
|
|
44
|
+
console.log(colors_1.default.underline('$ npx ' + args.join(' ')));
|
|
45
|
+
console.log();
|
|
46
|
+
try {
|
|
47
|
+
yield (0, execa_1.default)('npx', args, { cwd: (0, cli_helpers_1.getPaths)().api.base, shell: true, stdio: 'inherit', cleanup: true });
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
process.exit((e === null || e === void 0 ? void 0 : e.exitCode) || 1);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates a yargs command that passes all options to the ZenStack CLI command.
|
|
57
|
+
*/
|
|
58
|
+
function makePassthroughCommand(command) {
|
|
59
|
+
return {
|
|
60
|
+
command,
|
|
61
|
+
describe: `Run \`zenstack ${command} ...\``,
|
|
62
|
+
builder: (yargs) => {
|
|
63
|
+
return yargs
|
|
64
|
+
.strictOptions(false)
|
|
65
|
+
.strictCommands(false)
|
|
66
|
+
.strict(false)
|
|
67
|
+
.parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false });
|
|
68
|
+
},
|
|
69
|
+
handler: (_a) => __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
var { _, $0: _$0 } = _a, options = __rest(_a, ["_", "$0"]);
|
|
71
|
+
yield runCommand(command, options);
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
exports.makePassthroughCommand = makePassthroughCommand;
|
|
76
|
+
//# sourceMappingURL=cli-passthrough.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-passthrough.js","sourceRoot":"","sources":["../src/cli-passthrough.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wDAAkD;AAClD,oDAA4B;AAC5B,kDAA0B;AAG1B,8DAA8D;AAC9D,SAAe,UAAU,CAAC,OAAe,EAAE,OAAY;;QACnD,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACnC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC5B,oDAAoD;gBACpD,sCAAsC;gBACtC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7E,CAAC;QACL,CAAC;QAED,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,gBAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,gBAAM,CAAC,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,EAAE,CAAC;QAEd,IAAI,CAAC;YACD,MAAM,IAAA,eAAK,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACrG,8DAA8D;QAClE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,QAAQ,KAAI,CAAC,CAAC,CAAC;QACnC,CAAC;IACL,CAAC;CAAA;AAED;;GAEG;AACH,SAAgB,sBAAsB,CAAC,OAAe;IAClD,OAAO;QACH,OAAO;QACP,QAAQ,EAAE,kBAAkB,OAAO,QAAQ;QAC3C,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACf,OAAO,KAAK;iBACP,aAAa,CAAC,KAAK,CAAC;iBACpB,cAAc,CAAC,KAAK,CAAC;iBACrB,MAAM,CAAC,KAAK,CAAC;iBACb,mBAAmB,CAAC,EAAE,sBAAsB,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,EAAE,CAAO,EAA0B,EAAE,EAAE;gBAA9B,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,OAAc,EAAT,OAAO,cAAxB,WAA0B,CAAF;YACpC,MAAM,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACvC,CAAC,CAAA;KACJ,CAAC;AACN,CAAC;AAfD,wDAeC"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const cli_helpers_1 = require("@redwoodjs/cli-helpers");
|
|
16
|
+
const colors_1 = __importDefault(require("colors"));
|
|
17
|
+
const execa_1 = __importDefault(require("execa"));
|
|
18
|
+
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const listr2_1 = require("listr2");
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const terminal_link_1 = __importDefault(require("terminal-link"));
|
|
22
|
+
const ts_morph_1 = require("ts-morph");
|
|
23
|
+
const utils_1 = require("../utils");
|
|
24
|
+
function updateToml() {
|
|
25
|
+
return {
|
|
26
|
+
title: 'Updating redwood.toml...',
|
|
27
|
+
task: () => {
|
|
28
|
+
(0, cli_helpers_1.updateTomlConfig)('@zenstackhq/redwood');
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function installDependencies() {
|
|
33
|
+
return (0, utils_1.addApiPackages)([
|
|
34
|
+
{ pkg: 'zenstack', dev: true },
|
|
35
|
+
{ pkg: '@zenstackhq/runtime' },
|
|
36
|
+
{ pkg: '@zenstackhq/redwood' },
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
// copy schema.prisma to schema.zmodel, and update package.json
|
|
40
|
+
function bootstrapSchema() {
|
|
41
|
+
return {
|
|
42
|
+
title: 'Bootstrapping ZModel schema...',
|
|
43
|
+
task: () => {
|
|
44
|
+
const apiPaths = (0, cli_helpers_1.getPaths)().api;
|
|
45
|
+
const zmodel = path_1.default.join(path_1.default.dirname(apiPaths.dbSchema), 'schema.zmodel');
|
|
46
|
+
if (!fs_1.default.existsSync(zmodel)) {
|
|
47
|
+
fs_1.default.cpSync(apiPaths.dbSchema, zmodel);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.info(colors_1.default.blue(`Schema file "${path_1.default.relative((0, cli_helpers_1.getPaths)().base, zmodel)}" already exists. Skipping.`));
|
|
51
|
+
}
|
|
52
|
+
const pkgJson = path_1.default.join(apiPaths.base, 'package.json');
|
|
53
|
+
if (fs_1.default.existsSync(pkgJson)) {
|
|
54
|
+
const content = fs_1.default.readFileSync(pkgJson, 'utf-8');
|
|
55
|
+
const pkg = JSON.parse(content);
|
|
56
|
+
if (!pkg.zenstack) {
|
|
57
|
+
pkg.zenstack = {
|
|
58
|
+
schema: path_1.default.relative(apiPaths.base, zmodel),
|
|
59
|
+
prisma: path_1.default.relative(apiPaths.base, apiPaths.dbSchema),
|
|
60
|
+
};
|
|
61
|
+
fs_1.default.writeFileSync(pkgJson, JSON.stringify(pkg, null, 4));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// install ZenStack GraphQLYoga plugin
|
|
68
|
+
function installGraphQLPlugin() {
|
|
69
|
+
return {
|
|
70
|
+
title: 'Installing GraphQL plugin...',
|
|
71
|
+
task: () => __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
// locate "api/functions/graphql.[js|ts]"
|
|
73
|
+
let graphQlSourcePath;
|
|
74
|
+
const functionsDir = (0, cli_helpers_1.getPaths)().api.functions;
|
|
75
|
+
if (fs_1.default.existsSync(path_1.default.join(functionsDir, 'graphql.ts'))) {
|
|
76
|
+
graphQlSourcePath = path_1.default.join(functionsDir, 'graphql.ts');
|
|
77
|
+
}
|
|
78
|
+
else if (fs_1.default.existsSync(path_1.default.join(functionsDir, 'graphql.js'))) {
|
|
79
|
+
graphQlSourcePath = path_1.default.join(functionsDir, 'graphql.js');
|
|
80
|
+
}
|
|
81
|
+
if (!graphQlSourcePath) {
|
|
82
|
+
console.warn(colors_1.default.yellow(`Unable to find handler source file: ${path_1.default.join(functionsDir, 'graphql.(js|ts)')}`));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// add import
|
|
86
|
+
const project = new ts_morph_1.Project();
|
|
87
|
+
const graphQlSourceFile = project.addSourceFileAtPathIfExists(graphQlSourcePath);
|
|
88
|
+
let graphQlSourceFileChanged = false;
|
|
89
|
+
let identified = false;
|
|
90
|
+
const imports = graphQlSourceFile.getImportDeclarations();
|
|
91
|
+
if (!imports.some((i) => i.getModuleSpecifierValue() === '@zenstackhq/redwood')) {
|
|
92
|
+
graphQlSourceFile.addImportDeclaration({
|
|
93
|
+
moduleSpecifier: '@zenstackhq/redwood',
|
|
94
|
+
namedImports: ['useZenStack'],
|
|
95
|
+
});
|
|
96
|
+
graphQlSourceFileChanged = true;
|
|
97
|
+
}
|
|
98
|
+
// add "extraPlugins" option to `createGraphQLHandler` call
|
|
99
|
+
graphQlSourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression).forEach((expr) => {
|
|
100
|
+
var _a, _b;
|
|
101
|
+
if (identified) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (((_a = expr.getExpression().asKind(ts_morph_1.SyntaxKind.Identifier)) === null || _a === void 0 ? void 0 : _a.getText()) === 'createGraphQLHandler') {
|
|
105
|
+
const arg = (_b = expr.getArguments()[0]) === null || _b === void 0 ? void 0 : _b.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
|
|
106
|
+
if (arg) {
|
|
107
|
+
identified = true;
|
|
108
|
+
const props = arg.getProperties();
|
|
109
|
+
const pluginsProp = props.find((p) => { var _a; return ((_a = p.asKind(ts_morph_1.SyntaxKind.PropertyAssignment)) === null || _a === void 0 ? void 0 : _a.getName()) === 'extraPlugins'; });
|
|
110
|
+
if (pluginsProp) {
|
|
111
|
+
const pluginArr = pluginsProp.getInitializerIfKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
|
|
112
|
+
if (pluginArr) {
|
|
113
|
+
if (!pluginArr.getElements().some((e) => e.getText().includes('useZenStack'))) {
|
|
114
|
+
pluginArr.addElement('useZenStack(db)');
|
|
115
|
+
graphQlSourceFileChanged = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
arg.addPropertyAssignment({
|
|
121
|
+
name: 'extraPlugins',
|
|
122
|
+
initializer: '[useZenStack(db)]',
|
|
123
|
+
});
|
|
124
|
+
graphQlSourceFileChanged = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
if (!identified) {
|
|
130
|
+
console.warn(colors_1.default.yellow('Unable to determine how to install ZenStack GraphQL plugin. Please add it manually following https://zenstack.dev/docs/guides/redwood.'));
|
|
131
|
+
}
|
|
132
|
+
if (graphQlSourceFileChanged) {
|
|
133
|
+
graphQlSourceFile.formatText();
|
|
134
|
+
}
|
|
135
|
+
// create type-def file to add `db` into global context
|
|
136
|
+
let contextTypeDefCreated = false;
|
|
137
|
+
if (graphQlSourcePath.endsWith('.ts')) {
|
|
138
|
+
const typeDefPath = path_1.default.join((0, cli_helpers_1.getPaths)().api.src, 'zenstack.d.ts');
|
|
139
|
+
if (!fs_1.default.existsSync(typeDefPath)) {
|
|
140
|
+
const typeDefSourceFile = project.createSourceFile(typeDefPath, `import type { PrismaClient } from '@prisma/client'
|
|
141
|
+
|
|
142
|
+
declare module '@redwoodjs/graphql-server' {
|
|
143
|
+
interface GlobalContext {
|
|
144
|
+
db: PrismaClient
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
`);
|
|
148
|
+
typeDefSourceFile.formatText();
|
|
149
|
+
contextTypeDefCreated = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (graphQlSourceFileChanged || contextTypeDefCreated) {
|
|
153
|
+
yield project.save();
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// eject templates used for `yarn rw generate service`
|
|
159
|
+
function ejectServiceTemplates() {
|
|
160
|
+
return {
|
|
161
|
+
title: 'Ejecting service templates...',
|
|
162
|
+
task: () => __awaiter(this, void 0, void 0, function* () {
|
|
163
|
+
if (fs_1.default.existsSync(path_1.default.join((0, cli_helpers_1.getPaths)().api.base, 'generators', 'service'))) {
|
|
164
|
+
console.info(colors_1.default.blue('Service templates already ejected. Skipping.'));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
yield (0, execa_1.default)('yarn', ['rw', 'setup', 'generator', 'service'], { cwd: (0, cli_helpers_1.getPaths)().api.base });
|
|
168
|
+
const serviceTemplateTsFile = path_1.default.join((0, cli_helpers_1.getPaths)().api.base, 'generators', 'service', 'service.ts.template');
|
|
169
|
+
const serviceTemplateJsFile = path_1.default.join((0, cli_helpers_1.getPaths)().api.base, 'generators', 'service', 'service.js.template');
|
|
170
|
+
const serviceTemplateFile = fs_1.default.existsSync(serviceTemplateTsFile)
|
|
171
|
+
? serviceTemplateTsFile
|
|
172
|
+
: fs_1.default.existsSync(serviceTemplateJsFile)
|
|
173
|
+
? serviceTemplateJsFile
|
|
174
|
+
: undefined;
|
|
175
|
+
if (!serviceTemplateFile) {
|
|
176
|
+
console.warn(colors_1.default.red('Unable to find the ejected service template file.'));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// replace `db.` with `context.db.`
|
|
180
|
+
const templateContent = fs_1.default.readFileSync(serviceTemplateFile, 'utf-8');
|
|
181
|
+
const newTemplateContent = templateContent
|
|
182
|
+
.replace(/^import { db } from.*\n$/gm, '')
|
|
183
|
+
.replace(/return db\./g, 'return context.db.');
|
|
184
|
+
fs_1.default.writeFileSync(serviceTemplateFile, newTemplateContent);
|
|
185
|
+
}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function whatsNext() {
|
|
189
|
+
const zmodel = path_1.default.relative((0, cli_helpers_1.getPaths)().base, path_1.default.join(path_1.default.dirname((0, cli_helpers_1.getPaths)().api.dbSchema), 'schema.zmodel'));
|
|
190
|
+
const task = {
|
|
191
|
+
title: `What's next...`,
|
|
192
|
+
task: (_ctx, task) => {
|
|
193
|
+
task.title =
|
|
194
|
+
`What's next...\n\n` +
|
|
195
|
+
` - Install ${(0, terminal_link_1.default)('IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
|
|
196
|
+
` - Use "${zmodel}" to model database schema and access control.\n` +
|
|
197
|
+
` - Run \`yarn rw @zenstackhq generate\` to regenerate Prisma schema and client.\n` +
|
|
198
|
+
` - Learn ${(0, terminal_link_1.default)("how ZenStack extends Prisma's power", 'https://zenstack.dev/docs/the-complete-guide/part1')}.\n` +
|
|
199
|
+
` - Create a sample schema with \`yarn rw @zenstackhq sample\`.\n` +
|
|
200
|
+
` - Join ${(0, terminal_link_1.default)('Discord community', 'https://discord.gg/Ykhr738dUe')} for questions and updates.\n`;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
return task;
|
|
204
|
+
}
|
|
205
|
+
const setupCommand = {
|
|
206
|
+
command: 'setup',
|
|
207
|
+
describe: 'Set up ZenStack environment',
|
|
208
|
+
builder: (yargs) => yargs,
|
|
209
|
+
handler: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
210
|
+
const tasks = new listr2_1.Listr([
|
|
211
|
+
updateToml(),
|
|
212
|
+
installDependencies(),
|
|
213
|
+
bootstrapSchema(),
|
|
214
|
+
installGraphQLPlugin(),
|
|
215
|
+
ejectServiceTemplates(),
|
|
216
|
+
whatsNext(),
|
|
217
|
+
]);
|
|
218
|
+
try {
|
|
219
|
+
yield tasks.run();
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
console.error(colors_1.default.red(e.message));
|
|
224
|
+
process.exit((e === null || e === void 0 ? void 0 : e.exitCode) || 1);
|
|
225
|
+
}
|
|
226
|
+
}),
|
|
227
|
+
};
|
|
228
|
+
exports.default = setupCommand;
|
|
229
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EnhancementOptions, type AuthUser } from '@zenstackhq/runtime';
|
|
2
|
+
import { type Plugin } from 'graphql-yoga';
|
|
3
|
+
/**
|
|
4
|
+
* Plugin options
|
|
5
|
+
*/
|
|
6
|
+
export type ZenStackPluginOptions = EnhancementOptions;
|
|
7
|
+
/**
|
|
8
|
+
* A GraphQLYoga plugin that adds a ZenStack-enhanced PrismaClient into the context
|
|
9
|
+
* as `context.db`.
|
|
10
|
+
* @param db The original PrismaClient.
|
|
11
|
+
* @param getAuthUser A hook function for getting the current user. By default `context.currentUser` is used.
|
|
12
|
+
* @param options Options for creating the enhanced PrismaClient. See https://zenstack.dev/docs/reference/runtime-api#enhance for more details.
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export declare function useZenStack<PrismaClient extends object>(db: PrismaClient, getAuthUser?: (currentUser: unknown) => Promise<AuthUser>, options?: ZenStackPluginOptions): Plugin<{
|
|
16
|
+
currentUser: unknown;
|
|
17
|
+
db: PrismaClient;
|
|
18
|
+
}>;
|
package/dist/graphql.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.useZenStack = void 0;
|
|
13
|
+
const graphql_server_1 = require("@redwoodjs/graphql-server");
|
|
14
|
+
const runtime_1 = require("@zenstackhq/runtime");
|
|
15
|
+
/**
|
|
16
|
+
* A GraphQLYoga plugin that adds a ZenStack-enhanced PrismaClient into the context
|
|
17
|
+
* as `context.db`.
|
|
18
|
+
* @param db The original PrismaClient.
|
|
19
|
+
* @param getAuthUser A hook function for getting the current user. By default `context.currentUser` is used.
|
|
20
|
+
* @param options Options for creating the enhanced PrismaClient. See https://zenstack.dev/docs/reference/runtime-api#enhance for more details.
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
function useZenStack(db, getAuthUser, options) {
|
|
24
|
+
return {
|
|
25
|
+
onContextBuilding: () => {
|
|
26
|
+
return ({ context }) => __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
const user = getAuthUser ? yield getAuthUser(context.currentUser) : context.currentUser;
|
|
28
|
+
context.db = (0, runtime_1.enhance)(db, { user }, Object.assign({ errorTransformer: transformError }, options));
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
exports.useZenStack = useZenStack;
|
|
34
|
+
// Transforms ZenStack errors into appropriate RedwoodJS errors
|
|
35
|
+
function transformError(error) {
|
|
36
|
+
var _a, _b, _c;
|
|
37
|
+
if ((0, runtime_1.isPrismaClientKnownRequestError)(error) && error.code === runtime_1.PrismaErrorCode.CONSTRAINED_FAILED) {
|
|
38
|
+
if (((_a = error.meta) === null || _a === void 0 ? void 0 : _a.reason) === runtime_1.CrudFailureReason.ACCESS_POLICY_VIOLATION ||
|
|
39
|
+
((_b = error.meta) === null || _b === void 0 ? void 0 : _b.reason) === runtime_1.CrudFailureReason.RESULT_NOT_READABLE) {
|
|
40
|
+
return new graphql_server_1.ForbiddenError(error.message);
|
|
41
|
+
}
|
|
42
|
+
else if (((_c = error.meta) === null || _c === void 0 ? void 0 : _c.reason) === runtime_1.CrudFailureReason.DATA_VALIDATION_VIOLATION) {
|
|
43
|
+
return new runtime_1.ValidationError(error.message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return error;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=graphql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphql.js","sourceRoot":"","sources":["../src/graphql.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8DAA2D;AAC3D,iDAQ6B;AAQ7B;;;;;;;GAOG;AACH,SAAgB,WAAW,CACvB,EAAgB,EAChB,WAAyD,EACzD,OAA+B;IAE/B,OAAO;QACH,iBAAiB,EAAE,GAAG,EAAE;YACpB,OAAO,CAAO,EAAE,OAAO,EAAE,EAAE,EAAE;gBACzB,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAE,OAAO,CAAC,WAAwB,CAAC;gBACtG,OAAO,CAAC,EAAE,GAAG,IAAA,iBAAO,EAChB,EAAE,EACF,EAAE,IAAI,EAAE,kBAEJ,gBAAgB,EAAE,cAAc,IAC7B,OAAO,EAEjB,CAAC;YACN,CAAC,CAAA,CAAC;QACN,CAAC;KACJ,CAAC;AACN,CAAC;AApBD,kCAoBC;AAED,+DAA+D;AAC/D,SAAS,cAAc,CAAC,KAAc;;IAClC,IAAI,IAAA,yCAA+B,EAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,yBAAe,CAAC,kBAAkB,EAAE,CAAC;QAC9F,IACI,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,MAAK,2BAAiB,CAAC,uBAAuB;YAChE,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,MAAK,2BAAiB,CAAC,mBAAmB,EAC9D,CAAC;YACC,OAAO,IAAI,+BAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;aAAM,IAAI,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,MAAK,2BAAiB,CAAC,yBAAyB,EAAE,CAAC;YAC5E,OAAO,IAAI,yBAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.commands = void 0;
|
|
21
|
+
const cli_passthrough_1 = require("./cli-passthrough");
|
|
22
|
+
const setup_1 = __importDefault(require("./commands/setup"));
|
|
23
|
+
exports.commands = [
|
|
24
|
+
setup_1.default,
|
|
25
|
+
(0, cli_passthrough_1.makePassthroughCommand)('generate'),
|
|
26
|
+
(0, cli_passthrough_1.makePassthroughCommand)('info'),
|
|
27
|
+
(0, cli_passthrough_1.makePassthroughCommand)('format'),
|
|
28
|
+
(0, cli_passthrough_1.makePassthroughCommand)('repl'),
|
|
29
|
+
];
|
|
30
|
+
__exportStar(require("./graphql"), exports);
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,uDAA2D;AAC3D,6DAAqC;AAExB,QAAA,QAAQ,GAAG;IACpB,eAAK;IACL,IAAA,wCAAsB,EAAC,UAAU,CAAC;IAClC,IAAA,wCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,wCAAsB,EAAC,QAAQ,CAAC;IAChC,IAAA,wCAAsB,EAAC,MAAM,CAAC;CACjC,CAAC;AACF,4CAA0B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function setupPackage(): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
16
|
+
const helpers_1 = require("yargs/helpers");
|
|
17
|
+
const setup_1 = __importDefault(require("./commands/setup"));
|
|
18
|
+
function setupPackage() {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
yield (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
21
|
+
.scriptName('zenstack-setup')
|
|
22
|
+
// @ts-expect-error yargs types are wrong
|
|
23
|
+
.command('$0', 'set up ZenStack', setup_1.default.builder, setup_1.default.handler)
|
|
24
|
+
.parseAsync();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
exports.default = setupPackage;
|
|
28
|
+
//# sourceMappingURL=setup-package.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-package.js","sourceRoot":"","sources":["../src/setup-package.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,kDAA0B;AAC1B,2CAAwC;AACxC,6DAA4C;AAE5C,SAA8B,YAAY;;QACtC,MAAM,IAAA,eAAK,EAAC,IAAA,iBAAO,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aAC7B,UAAU,CAAC,gBAAgB,CAAC;YAC7B,yCAAyC;aACxC,OAAO,CAAC,IAAI,EAAE,iBAAiB,EAAE,eAAY,CAAC,OAAO,EAAE,eAAY,CAAC,OAAO,CAAC;aAC5E,UAAU,EAAE,CAAC;IACtB,CAAC;CAAA;AAND,+BAMC"}
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.addApiPackages = void 0;
|
|
16
|
+
const cli_helpers_1 = require("@redwoodjs/cli-helpers");
|
|
17
|
+
const execa_1 = __importDefault(require("execa"));
|
|
18
|
+
/**
|
|
19
|
+
* Utility for adding npm dependencies to "api" package
|
|
20
|
+
*/
|
|
21
|
+
const addApiPackages = (apiPackages) => ({
|
|
22
|
+
title: 'Adding required api packages...',
|
|
23
|
+
task: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
+
const devPkgs = apiPackages.filter((p) => p.dev).map((p) => p.pkg);
|
|
25
|
+
if (devPkgs.length > 0) {
|
|
26
|
+
yield (0, execa_1.default)('yarn', ['add', '-D', ...devPkgs], { cwd: (0, cli_helpers_1.getPaths)().api.base });
|
|
27
|
+
}
|
|
28
|
+
const runtimePkgs = apiPackages.filter((p) => !p.dev).map((p) => p.pkg);
|
|
29
|
+
if (runtimePkgs.length > 0) {
|
|
30
|
+
yield (0, execa_1.default)('yarn', ['add', ...runtimePkgs], { cwd: (0, cli_helpers_1.getPaths)().api.base });
|
|
31
|
+
}
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
exports.addApiPackages = addApiPackages;
|
|
35
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,wDAAkD;AAClD,kDAA0B;AAE1B;;GAEG;AACI,MAAM,cAAc,GAAG,CAAC,WAA6C,EAAE,EAAE,CAAC,CAAC;IAC9E,KAAK,EAAE,iCAAiC;IACxC,IAAI,EAAE,GAAS,EAAE;QACb,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAA,eAAK,EAAC,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACxE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAA,eAAK,EAAC,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/E,CAAC;IACL,CAAC,CAAA;CACJ,CAAC,CAAC;AAbU,QAAA,cAAc,kBAaxB"}
|
package/package.json
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenstackhq/redwood",
|
|
3
|
-
"displayName": "ZenStack RedwoodJS
|
|
4
|
-
"version": "
|
|
5
|
-
"description": "
|
|
3
|
+
"displayName": "ZenStack RedwoodJS Integration",
|
|
4
|
+
"version": "1.6.0",
|
|
5
|
+
"description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/zenstackhq/zenstack"
|
|
9
9
|
},
|
|
10
|
-
"main": "index.js",
|
|
11
|
-
"types": "index.d.ts",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"default": "./index.js",
|
|
15
|
-
"types": "./index.d.ts"
|
|
14
|
+
"default": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
16
|
},
|
|
17
|
-
"./
|
|
18
|
-
"default": "./
|
|
17
|
+
"./graphql": {
|
|
18
|
+
"default": "./dist/graphql.js",
|
|
19
|
+
"types": "./dist/graphql.d.ts"
|
|
19
20
|
},
|
|
20
21
|
"./package.json": {
|
|
21
22
|
"default": "./package.json"
|
|
22
23
|
}
|
|
23
24
|
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"
|
|
25
|
+
"bin": "bin/cli",
|
|
26
|
+
"engines": {
|
|
27
|
+
"redwoodjs": ">=6.0.0"
|
|
27
28
|
},
|
|
28
29
|
"author": {
|
|
29
30
|
"name": "ZenStack Team"
|
|
30
31
|
},
|
|
31
32
|
"homepage": "https://zenstack.dev",
|
|
32
33
|
"license": "MIT",
|
|
33
|
-
"bin": "node setup.js",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"colors": "1.4.0",
|
|
36
|
-
"
|
|
36
|
+
"ts-morph": "^16.0.0",
|
|
37
|
+
"@redwoodjs/cli-helpers": "^6.6.0",
|
|
38
|
+
"execa": "^5.0.0",
|
|
39
|
+
"listr2": "^6.0.0",
|
|
40
|
+
"terminal-link": "^2.0.0",
|
|
41
|
+
"yargs": "^17.7.2",
|
|
42
|
+
"@zenstackhq/runtime": "1.6.0"
|
|
37
43
|
},
|
|
38
44
|
"devDependencies": {
|
|
45
|
+
"@redwoodjs/graphql-server": "^6.6.0",
|
|
46
|
+
"@types/yargs": "^17.0.32",
|
|
39
47
|
"graphql-yoga": "^5.0.2"
|
|
40
48
|
},
|
|
41
49
|
"scripts": {
|
|
42
50
|
"clean": "rimraf dist",
|
|
43
|
-
"build": "pnpm lint --max-warnings=0 && pnpm clean && tsc &&
|
|
44
|
-
"watch": "
|
|
51
|
+
"build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && pnpm pack dist --pack-destination '../../../.build'",
|
|
52
|
+
"watch": "tsc --watch",
|
|
45
53
|
"lint": "eslint src --ext ts"
|
|
46
54
|
}
|
|
47
55
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getPaths } from '@redwoodjs/cli-helpers';
|
|
2
|
+
import colors from 'colors';
|
|
3
|
+
import execa from 'execa';
|
|
4
|
+
import { CommandModule } from 'yargs';
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
async function runCommand(command: string, options: any) {
|
|
8
|
+
const args = ['zenstack', command];
|
|
9
|
+
for (const [name, value] of Object.entries(options)) {
|
|
10
|
+
args.push(name.length > 1 ? `--${name}` : `-${name}`);
|
|
11
|
+
if (typeof value === 'string') {
|
|
12
|
+
// Make sure options that take multiple quoted words
|
|
13
|
+
// are passed to zenstack with quotes.
|
|
14
|
+
value.split(' ').length > 1 ? args.push(`"${value}"`) : args.push(value);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(colors.green('Running ZenStack CLI...'));
|
|
20
|
+
console.log(colors.underline('$ npx ' + args.join(' ')));
|
|
21
|
+
console.log();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await execa('npx', args, { cwd: getPaths().api.base, shell: true, stdio: 'inherit', cleanup: true });
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
} catch (e: any) {
|
|
27
|
+
process.exit(e?.exitCode || 1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a yargs command that passes all options to the ZenStack CLI command.
|
|
33
|
+
*/
|
|
34
|
+
export function makePassthroughCommand(command: string): CommandModule<unknown> {
|
|
35
|
+
return {
|
|
36
|
+
command,
|
|
37
|
+
describe: `Run \`zenstack ${command} ...\``,
|
|
38
|
+
builder: (yargs) => {
|
|
39
|
+
return yargs
|
|
40
|
+
.strictOptions(false)
|
|
41
|
+
.strictCommands(false)
|
|
42
|
+
.strict(false)
|
|
43
|
+
.parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false });
|
|
44
|
+
},
|
|
45
|
+
handler: async ({ _, $0: _$0, ...options }) => {
|
|
46
|
+
await runCommand(command, options);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { getPaths, updateTomlConfig } from '@redwoodjs/cli-helpers';
|
|
2
|
+
import colors from 'colors';
|
|
3
|
+
import execa from 'execa';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { Listr, ListrTask } from 'listr2';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import terminalLink from 'terminal-link';
|
|
8
|
+
import { Project, SyntaxKind, type PropertyAssignment } from 'ts-morph';
|
|
9
|
+
import type { CommandModule } from 'yargs';
|
|
10
|
+
import { addApiPackages } from '../utils';
|
|
11
|
+
|
|
12
|
+
function updateToml() {
|
|
13
|
+
return {
|
|
14
|
+
title: 'Updating redwood.toml...',
|
|
15
|
+
task: () => {
|
|
16
|
+
updateTomlConfig('@zenstackhq/redwood');
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function installDependencies() {
|
|
22
|
+
return addApiPackages([
|
|
23
|
+
{ pkg: 'zenstack', dev: true },
|
|
24
|
+
{ pkg: '@zenstackhq/runtime' },
|
|
25
|
+
{ pkg: '@zenstackhq/redwood' },
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// copy schema.prisma to schema.zmodel, and update package.json
|
|
30
|
+
function bootstrapSchema() {
|
|
31
|
+
return {
|
|
32
|
+
title: 'Bootstrapping ZModel schema...',
|
|
33
|
+
task: () => {
|
|
34
|
+
const apiPaths = getPaths().api;
|
|
35
|
+
const zmodel = path.join(path.dirname(apiPaths.dbSchema), 'schema.zmodel');
|
|
36
|
+
if (!fs.existsSync(zmodel)) {
|
|
37
|
+
fs.cpSync(apiPaths.dbSchema, zmodel);
|
|
38
|
+
} else {
|
|
39
|
+
console.info(
|
|
40
|
+
colors.blue(`Schema file "${path.relative(getPaths().base, zmodel)}" already exists. Skipping.`)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const pkgJson = path.join(apiPaths.base, 'package.json');
|
|
45
|
+
if (fs.existsSync(pkgJson)) {
|
|
46
|
+
const content = fs.readFileSync(pkgJson, 'utf-8');
|
|
47
|
+
const pkg = JSON.parse(content);
|
|
48
|
+
if (!pkg.zenstack) {
|
|
49
|
+
pkg.zenstack = {
|
|
50
|
+
schema: path.relative(apiPaths.base, zmodel),
|
|
51
|
+
prisma: path.relative(apiPaths.base, apiPaths.dbSchema),
|
|
52
|
+
};
|
|
53
|
+
fs.writeFileSync(pkgJson, JSON.stringify(pkg, null, 4));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// install ZenStack GraphQLYoga plugin
|
|
61
|
+
function installGraphQLPlugin() {
|
|
62
|
+
return {
|
|
63
|
+
title: 'Installing GraphQL plugin...',
|
|
64
|
+
task: async () => {
|
|
65
|
+
// locate "api/functions/graphql.[js|ts]"
|
|
66
|
+
let graphQlSourcePath: string | undefined;
|
|
67
|
+
const functionsDir = getPaths().api.functions;
|
|
68
|
+
if (fs.existsSync(path.join(functionsDir, 'graphql.ts'))) {
|
|
69
|
+
graphQlSourcePath = path.join(functionsDir, 'graphql.ts');
|
|
70
|
+
} else if (fs.existsSync(path.join(functionsDir, 'graphql.js'))) {
|
|
71
|
+
graphQlSourcePath = path.join(functionsDir, 'graphql.js');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!graphQlSourcePath) {
|
|
75
|
+
console.warn(
|
|
76
|
+
colors.yellow(`Unable to find handler source file: ${path.join(functionsDir, 'graphql.(js|ts)')}`)
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// add import
|
|
82
|
+
const project = new Project();
|
|
83
|
+
const graphQlSourceFile = project.addSourceFileAtPathIfExists(graphQlSourcePath)!;
|
|
84
|
+
let graphQlSourceFileChanged = false;
|
|
85
|
+
let identified = false;
|
|
86
|
+
|
|
87
|
+
const imports = graphQlSourceFile.getImportDeclarations();
|
|
88
|
+
if (!imports.some((i) => i.getModuleSpecifierValue() === '@zenstackhq/redwood')) {
|
|
89
|
+
graphQlSourceFile.addImportDeclaration({
|
|
90
|
+
moduleSpecifier: '@zenstackhq/redwood',
|
|
91
|
+
namedImports: ['useZenStack'],
|
|
92
|
+
});
|
|
93
|
+
graphQlSourceFileChanged = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// add "extraPlugins" option to `createGraphQLHandler` call
|
|
97
|
+
graphQlSourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((expr) => {
|
|
98
|
+
if (identified) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (expr.getExpression().asKind(SyntaxKind.Identifier)?.getText() === 'createGraphQLHandler') {
|
|
103
|
+
const arg = expr.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
104
|
+
if (arg) {
|
|
105
|
+
identified = true;
|
|
106
|
+
const props = arg.getProperties();
|
|
107
|
+
const pluginsProp = props.find(
|
|
108
|
+
(p): p is PropertyAssignment =>
|
|
109
|
+
p.asKind(SyntaxKind.PropertyAssignment)?.getName() === 'extraPlugins'
|
|
110
|
+
);
|
|
111
|
+
if (pluginsProp) {
|
|
112
|
+
const pluginArr = pluginsProp.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
|
|
113
|
+
if (pluginArr) {
|
|
114
|
+
if (!pluginArr.getElements().some((e) => e.getText().includes('useZenStack'))) {
|
|
115
|
+
pluginArr.addElement('useZenStack(db)');
|
|
116
|
+
graphQlSourceFileChanged = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
arg.addPropertyAssignment({
|
|
121
|
+
name: 'extraPlugins',
|
|
122
|
+
initializer: '[useZenStack(db)]',
|
|
123
|
+
});
|
|
124
|
+
graphQlSourceFileChanged = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!identified) {
|
|
131
|
+
console.warn(
|
|
132
|
+
colors.yellow(
|
|
133
|
+
'Unable to determine how to install ZenStack GraphQL plugin. Please add it manually following https://zenstack.dev/docs/guides/redwood.'
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (graphQlSourceFileChanged) {
|
|
139
|
+
graphQlSourceFile.formatText();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// create type-def file to add `db` into global context
|
|
143
|
+
let contextTypeDefCreated = false;
|
|
144
|
+
if (graphQlSourcePath.endsWith('.ts')) {
|
|
145
|
+
const typeDefPath = path.join(getPaths().api.src, 'zenstack.d.ts');
|
|
146
|
+
if (!fs.existsSync(typeDefPath)) {
|
|
147
|
+
const typeDefSourceFile = project.createSourceFile(
|
|
148
|
+
typeDefPath,
|
|
149
|
+
`import type { PrismaClient } from '@prisma/client'
|
|
150
|
+
|
|
151
|
+
declare module '@redwoodjs/graphql-server' {
|
|
152
|
+
interface GlobalContext {
|
|
153
|
+
db: PrismaClient
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
`
|
|
157
|
+
);
|
|
158
|
+
typeDefSourceFile.formatText();
|
|
159
|
+
contextTypeDefCreated = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (graphQlSourceFileChanged || contextTypeDefCreated) {
|
|
164
|
+
await project.save();
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// eject templates used for `yarn rw generate service`
|
|
171
|
+
function ejectServiceTemplates() {
|
|
172
|
+
return {
|
|
173
|
+
title: 'Ejecting service templates...',
|
|
174
|
+
task: async () => {
|
|
175
|
+
if (fs.existsSync(path.join(getPaths().api.base, 'generators', 'service'))) {
|
|
176
|
+
console.info(colors.blue('Service templates already ejected. Skipping.'));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await execa('yarn', ['rw', 'setup', 'generator', 'service'], { cwd: getPaths().api.base });
|
|
181
|
+
const serviceTemplateTsFile = path.join(
|
|
182
|
+
getPaths().api.base,
|
|
183
|
+
'generators',
|
|
184
|
+
'service',
|
|
185
|
+
'service.ts.template'
|
|
186
|
+
);
|
|
187
|
+
const serviceTemplateJsFile = path.join(
|
|
188
|
+
getPaths().api.base,
|
|
189
|
+
'generators',
|
|
190
|
+
'service',
|
|
191
|
+
'service.js.template'
|
|
192
|
+
);
|
|
193
|
+
const serviceTemplateFile = fs.existsSync(serviceTemplateTsFile)
|
|
194
|
+
? serviceTemplateTsFile
|
|
195
|
+
: fs.existsSync(serviceTemplateJsFile)
|
|
196
|
+
? serviceTemplateJsFile
|
|
197
|
+
: undefined;
|
|
198
|
+
|
|
199
|
+
if (!serviceTemplateFile) {
|
|
200
|
+
console.warn(colors.red('Unable to find the ejected service template file.'));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// replace `db.` with `context.db.`
|
|
205
|
+
const templateContent = fs.readFileSync(serviceTemplateFile, 'utf-8');
|
|
206
|
+
const newTemplateContent = templateContent
|
|
207
|
+
.replace(/^import { db } from.*\n$/gm, '')
|
|
208
|
+
.replace(/return db\./g, 'return context.db.');
|
|
209
|
+
fs.writeFileSync(serviceTemplateFile, newTemplateContent);
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function whatsNext() {
|
|
215
|
+
const zmodel = path.relative(getPaths().base, path.join(path.dirname(getPaths().api.dbSchema), 'schema.zmodel'));
|
|
216
|
+
const task: ListrTask = {
|
|
217
|
+
title: `What's next...`,
|
|
218
|
+
task: (_ctx, task) => {
|
|
219
|
+
task.title =
|
|
220
|
+
`What's next...\n\n` +
|
|
221
|
+
` - Install ${terminalLink('IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
|
|
222
|
+
` - Use "${zmodel}" to model database schema and access control.\n` +
|
|
223
|
+
` - Run \`yarn rw @zenstackhq generate\` to regenerate Prisma schema and client.\n` +
|
|
224
|
+
` - Learn ${terminalLink(
|
|
225
|
+
"how ZenStack extends Prisma's power",
|
|
226
|
+
'https://zenstack.dev/docs/the-complete-guide/part1'
|
|
227
|
+
)}.\n` +
|
|
228
|
+
` - Create a sample schema with \`yarn rw @zenstackhq sample\`.\n` +
|
|
229
|
+
` - Join ${terminalLink(
|
|
230
|
+
'Discord community',
|
|
231
|
+
'https://discord.gg/Ykhr738dUe'
|
|
232
|
+
)} for questions and updates.\n`;
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
return task;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const setupCommand: CommandModule<unknown> = {
|
|
239
|
+
command: 'setup',
|
|
240
|
+
describe: 'Set up ZenStack environment',
|
|
241
|
+
builder: (yargs) => yargs,
|
|
242
|
+
handler: async () => {
|
|
243
|
+
const tasks = new Listr([
|
|
244
|
+
updateToml(),
|
|
245
|
+
installDependencies(),
|
|
246
|
+
bootstrapSchema(),
|
|
247
|
+
installGraphQLPlugin(),
|
|
248
|
+
ejectServiceTemplates(),
|
|
249
|
+
whatsNext(),
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
await tasks.run();
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
255
|
+
} catch (e: any) {
|
|
256
|
+
console.error(colors.red(e.message));
|
|
257
|
+
process.exit(e?.exitCode || 1);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export default setupCommand;
|
package/src/graphql.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ForbiddenError } from '@redwoodjs/graphql-server';
|
|
2
|
+
import {
|
|
3
|
+
CrudFailureReason,
|
|
4
|
+
EnhancementOptions,
|
|
5
|
+
PrismaErrorCode,
|
|
6
|
+
ValidationError,
|
|
7
|
+
enhance,
|
|
8
|
+
isPrismaClientKnownRequestError,
|
|
9
|
+
type AuthUser,
|
|
10
|
+
} from '@zenstackhq/runtime';
|
|
11
|
+
import { type Plugin } from 'graphql-yoga';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Plugin options
|
|
15
|
+
*/
|
|
16
|
+
export type ZenStackPluginOptions = EnhancementOptions;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A GraphQLYoga plugin that adds a ZenStack-enhanced PrismaClient into the context
|
|
20
|
+
* as `context.db`.
|
|
21
|
+
* @param db The original PrismaClient.
|
|
22
|
+
* @param getAuthUser A hook function for getting the current user. By default `context.currentUser` is used.
|
|
23
|
+
* @param options Options for creating the enhanced PrismaClient. See https://zenstack.dev/docs/reference/runtime-api#enhance for more details.
|
|
24
|
+
* @returns
|
|
25
|
+
*/
|
|
26
|
+
export function useZenStack<PrismaClient extends object>(
|
|
27
|
+
db: PrismaClient,
|
|
28
|
+
getAuthUser?: (currentUser: unknown) => Promise<AuthUser>,
|
|
29
|
+
options?: ZenStackPluginOptions
|
|
30
|
+
): Plugin<{ currentUser: unknown; db: PrismaClient }> {
|
|
31
|
+
return {
|
|
32
|
+
onContextBuilding: () => {
|
|
33
|
+
return async ({ context }) => {
|
|
34
|
+
const user = getAuthUser ? await getAuthUser(context.currentUser) : (context.currentUser as AuthUser);
|
|
35
|
+
context.db = enhance(
|
|
36
|
+
db,
|
|
37
|
+
{ user },
|
|
38
|
+
{
|
|
39
|
+
errorTransformer: transformError,
|
|
40
|
+
...options,
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Transforms ZenStack errors into appropriate RedwoodJS errors
|
|
49
|
+
function transformError(error: unknown) {
|
|
50
|
+
if (isPrismaClientKnownRequestError(error) && error.code === PrismaErrorCode.CONSTRAINED_FAILED) {
|
|
51
|
+
if (
|
|
52
|
+
error.meta?.reason === CrudFailureReason.ACCESS_POLICY_VIOLATION ||
|
|
53
|
+
error.meta?.reason === CrudFailureReason.RESULT_NOT_READABLE
|
|
54
|
+
) {
|
|
55
|
+
return new ForbiddenError(error.message);
|
|
56
|
+
} else if (error.meta?.reason === CrudFailureReason.DATA_VALIDATION_VIOLATION) {
|
|
57
|
+
return new ValidationError(error.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return error;
|
|
61
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { makePassthroughCommand } from './cli-passthrough';
|
|
2
|
+
import setup from './commands/setup';
|
|
3
|
+
|
|
4
|
+
export const commands = [
|
|
5
|
+
setup,
|
|
6
|
+
makePassthroughCommand('generate'),
|
|
7
|
+
makePassthroughCommand('info'),
|
|
8
|
+
makePassthroughCommand('format'),
|
|
9
|
+
makePassthroughCommand('repl'),
|
|
10
|
+
];
|
|
11
|
+
export * from './graphql';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
3
|
+
import setupCommand from './commands/setup';
|
|
4
|
+
|
|
5
|
+
export default async function setupPackage() {
|
|
6
|
+
await yargs(hideBin(process.argv))
|
|
7
|
+
.scriptName('zenstack-setup')
|
|
8
|
+
// @ts-expect-error yargs types are wrong
|
|
9
|
+
.command('$0', 'set up ZenStack', setupCommand.builder, setupCommand.handler)
|
|
10
|
+
.parseAsync();
|
|
11
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getPaths } from '@redwoodjs/cli-helpers';
|
|
2
|
+
import execa from 'execa';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility for adding npm dependencies to "api" package
|
|
6
|
+
*/
|
|
7
|
+
export const addApiPackages = (apiPackages: { pkg: string; dev?: boolean }[]) => ({
|
|
8
|
+
title: 'Adding required api packages...',
|
|
9
|
+
task: async () => {
|
|
10
|
+
const devPkgs = apiPackages.filter((p) => p.dev).map((p) => p.pkg);
|
|
11
|
+
if (devPkgs.length > 0) {
|
|
12
|
+
await execa('yarn', ['add', '-D', ...devPkgs], { cwd: getPaths().api.base });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const runtimePkgs = apiPackages.filter((p) => !p.dev).map((p) => p.pkg);
|
|
16
|
+
if (runtimePkgs.length > 0) {
|
|
17
|
+
await execa('yarn', ['add', ...runtimePkgs], { cwd: getPaths().api.base });
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
});
|
package/tsconfig.json
ADDED
package/index.d.ts
DELETED
package/index.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useZenStack = void 0;
|
|
4
|
-
const runtime_1 = require("@zenstackhq/runtime");
|
|
5
|
-
function useZenStack(db) {
|
|
6
|
-
return {
|
|
7
|
-
onContextBuilding() {
|
|
8
|
-
return ({ context }) => {
|
|
9
|
-
context.db = (0, runtime_1.enhance)(db, { user: context === null || context === void 0 ? void 0 : context.currentUser });
|
|
10
|
-
};
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
exports.useZenStack = useZenStack;
|
|
15
|
-
//# sourceMappingURL=index.js.map
|
package/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAA6D;AAG7D,SAAgB,WAAW,CACvB,EAAgB;IAEhB,OAAO;QACH,iBAAiB;YACb,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;gBACnB,OAAO,CAAC,EAAE,GAAG,IAAA,iBAAO,EAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,EAAE,CAAC,CAAC;YAC7D,CAAC,CAAC;QACN,CAAC;KACJ,CAAC;AACN,CAAC;AAVD,kCAUC"}
|
package/setup.d.ts
DELETED
|
File without changes
|
package/setup.js
DELETED
package/setup.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":";AAAA,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC"}
|