Skip to content

Commit

Permalink
add demo of field resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-burel committed Dec 13, 2021
1 parent 08be514 commit 300490e
Show file tree
Hide file tree
Showing 10 changed files with 306,975 additions and 267,512 deletions.
211,803 changes: 111,107 additions & 100,696 deletions .vn/scripts/db/reset.js

Large diffs are not rendered by default.

362,456 changes: 195,697 additions & 166,759 deletions .vn/scripts/db/seed.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .vn/scripts/link-vulcan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
./node_modules/.bin/yalc link @vulcanjs/multi-env-demo
./node_modules/.bin/yalc link @vulcanjs/permissions
./node_modules/.bin/yalc link @vulcanjs/react-ui
./node_modules/.bin/yalc link @vulcanjs/react-ui-material
./node_modules/.bin/yalc link @vulcanjs/react-hooks
./node_modules/.bin/yalc link @vulcanjs/schema
./node_modules/.bin/yalc link @vulcanjs/utils
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"debug": "NODE_OPTIONS='--inspect' next",
"dev": "next",
"dev:test": "cross-env NODE_ENV=test next # Start app in test + dev mode",
"link-vulcan": "./.vn/scripts/link-vulcan.sh # for linking a local copy of Vulcan NPM monorepo (don't forget to publish in Vulcan NPM first)",
"link:vulcan": "./.vn/scripts/link-vulcan.sh # for linking a local copy of Vulcan NPM monorepo (don't forget to publish in Vulcan NPM first)",
"lint": "yarn run next lint",
"mkdir:reports": "rm -Rf reports && mkdir reports || true # intermediate script",
"mongo": "yarn run start:mongo  # shortcut for start:mongo",
Expand All @@ -54,7 +54,7 @@
"upgrade:vulcan": "yarn upgrade --pattern '@vulcanjs/*'",
"typecheck-watch": "tsc --noEmit --p src/tsconfig.json -w",
"typecheck": "tsc --noEmit --p src/tsconfig.json # in case of error with @vulcanjs/* package, check that src/types (eg simpl-schema) are up-to-date with vulcan-npm",
"update-link-vulcan": "yalc update"
"link:vulcan:update": "yalc update"
},
"dependencies": {
"@apollo/client": "^3.2.0",
Expand Down Expand Up @@ -104,6 +104,7 @@
"postcss-nested": "^4.2.1",
"querystring": "^0.2.1",
"react": "^17.0.1",
"react-bootstrap-typeahead": "^6.0.0-alpha.4",
"react-dom": "^17.0.1",
"react-hook-form": "4.9.8",
"react-i18next": "^11.5.0",
Expand Down
4 changes: 2 additions & 2 deletions src/content/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ Vulcan NPM is relying on Lerna.

We rely on Yalc to set a local registry that simulates NPM behaviour. This palliates limitations of `yarn link`, which is simpler, but creates unexpected issues with duplicate `node_modules` (the symlinked package keeps using `node_modules` from the monorepo instead of your app, leading to duplicate React instance, wrong Webpack version...).

- In Vulcan NPM, run `yarn run local-publish`. This will use `yalc` to simulate a local NPM registry (quite similarly to METEOR_PACKAGE_DIRS)
- In Vulcan Next, run `yarn run link-vulcan`. This will symlink Vulcan packages to the local registry.
- In Vulcan NPM, run `yarn run publish:local`. This will use `yalc` to simulate a local NPM registry (quite similarly to METEOR_PACKAGE_DIRS)
- In Vulcan Next, run `yarn run link:vulcan`. This will symlink Vulcan packages to the local registry.

If you modify Vulcan NPM:

Expand Down
45 changes: 12 additions & 33 deletions src/models/advancedModel.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,16 @@ import {
} from "@vulcanjs/graphql/server";
import { createMongooseConnector } from "@vulcanjs/mongo";
import merge from "lodash/merge";
import { modelDef as modelDefShared } from "./sampleModel";
import {
schema as schemaShared,
modelDef as modelDefShared,
} from "./advancedModel";

const schema: VulcanGraphqlSchemaServer = {
_id: {
type: String,
optional: true,
canRead: ["guests"],
},
userId: {
type: String,
optional: true,
canRead: ["guests"],
},
createdAt: {
type: Date,
optional: true,
canRead: ["admins"],
onCreate: () => {
return new Date();
},
},
someField: {
type: String,
optional: true,
canRead: ["guests"],
canUpdate: ["admins"],
canCreate: ["owners"],
searchable: true,
},
};
const schema: VulcanGraphqlSchemaServer = merge(
{},
schemaShared,
{} as VulcanGraphqlSchemaServer
);

export interface SampleModelType extends VulcanDocument {
someField: string;
Expand All @@ -51,9 +31,8 @@ const modelDef: CreateGraphqlModelOptionsServer = merge({}, modelDefShared, {
graphql: {
/* ...*/
},
});
} as CreateGraphqlModelOptionsServer);
export const SampleModel = createGraphqlModelServer(modelDef);

export const SampleModelConnector = createMongooseConnector<SampleModelType>(
SampleModel
);
export const SampleModelConnector =
createMongooseConnector<SampleModelType>(SampleModel);
2 changes: 1 addition & 1 deletion src/models/advancedModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
CreateGraphqlModelOptionsShared,
} from "@vulcanjs/graphql";

const schema: VulcanSchema = {
export const schema: VulcanSchema = {
_id: {
type: String,
optional: true,
Expand Down
62 changes: 56 additions & 6 deletions src/models/sampleModel.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
CreateGraphqlModelOptionsServer,
createGraphqlModelServer,
VulcanGraphqlSchemaServer,
getModel,
getModelConnector,
} from "@vulcanjs/graphql/server";
import { createMongooseConnector } from "@vulcanjs/mongo";
import merge from "lodash/merge";
Expand All @@ -15,6 +17,7 @@ import {
SampleModelType,
schema as commonSchema,
} from "./sampleModel";
import { User, UserConnector } from "./user.server";

const schema: VulcanGraphqlSchemaServer = merge({}, commonSchema, {
// An API-only field, that will appear in the graphql schema
Expand All @@ -27,7 +30,50 @@ const schema: VulcanGraphqlSchemaServer = merge({}, commonSchema, {
canCreate: ["owners"],
searchable: true,
},
});
// If relationDemoUserId matches an existing user, this field can resolve it
// NOTE: the match will be done on "_id" field, this is not configurable yet
relationDemoUserId: {
type: String,
relation: {
fieldName: "resolvedFieldFromRelation",
kind: "hasOne",
model: User,
},
},
/**
* Demo of an avanced field resolver
*
* Use only as a last resort for advanced use case,
* prefer relations for more basic use cases
*/
someId: {
type: String,
optional: true,
canRead: ["admins"],
resolveAs: {
fieldName: "resolvedField",
// Return type of your field
typeName: "String",
// Will keep "someId" in the graphql schema
addOriginalField: true,
arguments: "someArgument: String, anotherArgument: Int",
description: "A resolved field",
resolver: async (document, args, context, info) => {
// Here, you can get a value based on "document.someId" (or any other field)
// "context" contains:
// - currentUser and userId for authenticated user
// - HTTP request
// - each model and connector (but you should import them explicitely, this only is used internally by Vulcan)
// Permissions are automatically checked for you based on "canRead" field
const allUsers = UserConnector.find({}, { limit: 10 });
console.log("allUsers", allUsers);
return `
Initial Id is: ${document.someId}
Args are: ${args.someArgument}, ${args.anotherArgument}`;
},
},
},
} as VulcanGraphqlSchemaServer);

export interface SampleModelTypeServer extends SampleModelType {
someServerOnlyField: string;
Expand All @@ -37,10 +83,14 @@ const modelDef: CreateGraphqlModelOptionsServer = merge({}, commonModelDef, {
schema,
// add other server only options here,
// like callbacks
graphql: {},
});
graphql: {
...commonModelDef.graphql, // NOT: not mandatory but will please TS until we can figure nested partial types
// Expert feature: you can customize resolvers for CRUD operations here
// mutationResolvers: { },
// queryResolvers: {}
},
} as Partial<CreateGraphqlModelOptionsServer>);
export const SampleModel = createGraphqlModelServer(modelDef);

export const SampleModelConnector = createMongooseConnector<SampleModelType>(
SampleModel
);
export const SampleModelConnector =
createMongooseConnector<SampleModelType>(SampleModel);
23 changes: 19 additions & 4 deletions src/pages/learn/server-only-models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,24 @@ However, some code should stay private.
For example, the code that process user password belongs to the server.
It should not be included in the client bundle and shared to users.

## Discover the User model server code
## Discover the User and Sample model server code

The User model is a good example of server-only features.
The User model is a good example of real-life server-only features.
The Sample model is also including demonstration of how you could use Vulcan most advanced features
to customize your graphql schema.

Open `src/models/user.server.ts`.
Open `src/models/user.server.ts` and `src/models/sampleModel.server.ts`.

You'll see:

- **Model specific logic**: a bunch of functions related to users
- **Server-only fields**: fields you **don't** want to see in the user interface
- **Server callbacks**: functions that are called on certain events (creation, update, deletion...),
like sending an email on success, modifying data before storing them etc.
- **Relation field-resolvers**: Handle one-to-one and one-to-many relationships elegantly using the relation syntax
- **Custom field-resolvers**: some data might **not** be stored outside of your database.
For instance, you might want to get information about the user from the Twitter API.
To do so, you can use a custom field resolver, with the `resolveAs` property.
To do so, you can use a custom field resolver, with the resolveAs` property.

It is a very advanced model: it handles user signup and authentication.
Since those features are related to security, you want them to
Expand All @@ -52,6 +55,18 @@ and Mongo database. Awesome!
Notice the `.server`. In Vulcan, whenever you see a folder named `server`, or
a file named `*.server.ts`, it means that it contains server-only code.

## Customizing the graphQL schema

Have some really custom logic that don't fit in Vulcan Model architecture?

No problem, Vulcan Next has "Just Do It" philosophy: you have full control
over your GraphQL schema and Apollo server.

Just check `src/pages/api/graphql.ts` to understand the setup.

Models are meant to make your life easier in the early stage of developing your application.
But you are completely free to _not_ use them and write your GraphQL API as you wish.

## Go to step 5

First, please answer this question:
Expand Down
Loading

0 comments on commit 300490e

Please sign in to comment.