Git Product home page Git Product logo

vulcan-npm's Introduction

Vulcan NPM

What is Vuclan

Vulcan is an open source full-stack JavaScript toolkit.

It brings everything you need to create modern web applications on top of modern frameworks.

This monorepo is where all the development happens. Only contributor should need to clone it, if you want to create an app with Vulcan:

See Vulcan docs for detailed documentation: https://vulcan-docs.vercel.app/

Contribute

After you've read the README, also check the contribution documentation for more information about contributions.

Architecture of the monorepo

  • Turborepo to run scripts efficiently
  • Yarn 3 workspaces
  • TypeScript
  • Tsup (Esbuild + Rollup) for bundling packages
  • Packages in packages and starter apps in starters
  • Storybook
  • Jest client and server tests
  • Docusaurus documentation

From Lerna to Yarn 2

RIP lerna

Lerna is officially deprecated. Instead we are progressively replacing it with Yarn 2 workspaces.

# 1. Install Node 16.10+, then:
corepack enable
yarn set version stable

Plugins are already installed, since some parts of the ".yarn" folder are stored in the repo (Yarn executable, plugins etc.)

Install Vulcan NPM and start coding

Please use Yarn

yarn # will install + bootstrap learn
yarn run build # build all packages

Now you can either run Storybook yarn run storybook or unit tests yarn run test and start working.

Plug to another local application

If you want to connect your local Vulcan NPM install to an existing application, please check Vulcan Next documentation.

It's a 2 step process:

  • you publish the packages locally using Yalc yarn run publish:local
  • you install them, using Yalc, in your app.

We use Yalc and not yarn link because linking is not sufficient, it raises a lot of issues with locally installed packages.

Now @vulcanjs/xxx will be available in your own application.

Windows

To use a testing database on windows, you could encounter an unexpected issue ; a solution is to download Visual C++ redistribuable. See nodkz/mongodb-memory-server#475

Ubuntu updates

Best solution is to use a dockerized Mongo instead or stick to Ubuntu 20.

Resources


Powered by Vercel

vulcan-npm's People

Contributors

dependabot[bot] avatar eric-burel avatar graemefulton avatar neobii avatar sachag avatar timi-duban avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

vulcan-npm's Issues

Investigate using string ids instead of ObjectId in Mongoose connector

Is your feature request related to a problem? Please describe.

Meteor uses string ids as a default: https://docs.meteor.com/api/collections.html

idGeneration String
The method of generating the _id fields of new documents in this collection. Possible values:

'STRING': random strings
'MONGO': random Mongo.ObjectID values

But Mongo uses Mongo.ObjectID.

So in Vulcan NPM:

  • _id is an ObjectId in the backend
  • _id is a string in graphql, after serialization of the response

This leads to issues with isomorphic methods like Users.owns

Describe the solution you'd like
Use string ids as a default?
Or converting?

Describe alternatives you've considered

Altering our mongoose connector?

We may want to alter the connector so it uses string ids.
I think the best way is to do this on creation, but it could be safer to do it during find. Connectors are expected to return the same type whatever the technology, so we can expect Mongoose connector to return string _id.

The problem arise in custom resolvers, if user want to call Mongo methods, they may accidentally return an ObjectId. Maybe TypeScript can catch this already?

=> this might be necessary anyway at least for "find" and "findOne", for typing consistency, connectors should return a VulcanDocument so a string _id

Handling the possibility of the _id to be an ObjectId in all functions?

Simplest approach might be to introduce a compareIds method that uses useEqual under the hookd, and use it whenever needed + improve VulcanDocument typing so it can be either string or ObjectId (but beware of not introducing dependency to mongo).

This is a bit annoying client-side though, where _id is guaranteed to be a string.

Configuring mongo to use strings as a default, at top level?

The problem is that it alters mongoose default behaviour. The good part is that it will guarantee string ids everywhere even for custom calls.

Shortcut syntax for `{_eq:}` filter

Is your feature request related to a problem? Please describe.
Currently you cannot write { input: { filter: { foo: 2}=} in a multi filter. You have to use the _eq operator { input: { filter: { foo: {_eq:2 }}}}.

Describe the solution you'd like
Allow a shortcut syntax for those fields. See the FieldSelector type in packages/graphql/typings.ts, I've commented the native values and only allow a condition.

Describe alternatives you've considered
I think the problem is that you cannot have union types for input in Graphql: it's difficult to accept both an object or a native value as input. To be confirmed.

Investigate cross-site Set-Cookies

Is your feature request related to a problem? Please describe.
When the cookie
https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Set-Cookie/SameSite
Describe the solution you'd like
Possibility to configure the SameSite parameter + documentation on how to have the API and the frontend on different domain, eg for a shared API.

It can also affects app with a Meteor backend and a Next frontend.
Describe alternatives you've considered
Hosting on the same root domain should solve this issue.
Additional context
Add any other context or screenshots about the feature request here.

Support schema serialization/deserialization

Is your feature request related to a problem? Please describe.
Some fields are using constructors such as String, Boolean...
Describe the solution you'd like

  • Allow to serialize/deserialize existing schema with a script
  • Allow to pass string values instead for types

This way we can store schemas.

Additional context
Add any other context or screenshots about the feature request here.

Dev mode with file watching

Is your feature request related to a problem? Please describe.
There is no dev mode so we must manually rebuild.
Describe the solution you'd like
yarn run dev should run the build system in watch mode, in order to rebuild relevant package on change
Describe alternatives you've considered
Check Lerna documentation for how we can do that, or existing Lerna project like Babel, Jest...

Access Apollo client in update function => watched queries won't be updated

In Vulcan, commit "3ac1a60b62fb78fd1d71d72f00b11172f34a0ed4" replaced cache by client in Apollo multi query updater.
For the record, this function updates list when you create a new item locally.
As a default, Apollo only provides you the cache. That's enough in most case, but updating the cache WON'T update watched queries. In this case, you have to call writeQuery on the client object and not just the cache

Our current implementation of Apollo client, and the method to access it, relies on @vulcan/next-apollo, which is a Next specific package (see #6), but we don't want @vulcan/react-hooks to depend on it.

The problem is that Apollo doesn't really provide a way to access the default Apollo client either, and does not include it in the update call parameter.
Technically, since you are in the mutation, there is such a client, it is just not exposed in the API.

To be fully solved, this issue must be solved at Apollo client level. Another solution is that the cache should actually trigger watched queries too?

Possibly related:

hasMany/hasOne do not check document access authorization on resolved documents

Is your feature request related to a problem? Please describe.

  • We should share more code between single, multi, hasOne, and hasMany resolver, a bit like we do for mutators.
  • We need to check that hasMany/hasOne cannot be resolved on field user is not allowed to read (normally it's the case already but this deserves a unit test on its own=
  • We need to filter the list of documents user can read in relations, mainly when the permission is "owners", this is not the case yet (we however filter fields correctly in the related document)

Use package.json exports when ts support lands in

Is your feature request related to a problem? Please describe.
@see https://stackoverflow.com/questions/63058081/package-json-with-multiple-entrypoints
@see https://blog.mozilla.org/data/2021/04/07/this-week-in-glean-publishing-glean-js/

This would allow us to use index.server.ts files instead of server/index.ts, and get rid of the typesVersions hack we currently use to handle both server and client exports.

Also see this Lerna ticket for the "fake alias" hack: @see lerna/lerna#3006

SmartForm + Components registering using Context

To do

Step 1: get the form to work with Vulcan API

  • Document a bit by creating a readme
  • Check if there is a way to automatically reload data in the update form => useUpdate do not seem to be run again when the data has been updated elsewhere
  • Check if textarea/enter keypress is ok, see packages/react-ui/components/form/FormComponentInner.tsx
  • Currently we need to explicitely pass currentUser to the SmartForm => it should be provided in the context? The User model belongs to Vulcan Next so we need to carefully check for this dependency.
  • In packages/react-components/components/form/Form/fields.ts, move the side effect of handleFieldPath (default value setting) somewhere else, to make the function pure
  • Allow to have the same form twice. For this, we need to make sure that input's id are unique, like "context + name"' (where it's user responsibility to give a unique "context" to their form). Note that id is mandatory for inputs so we can label them respecting a11y.
  • Pass callbacks through context, provides hooks etc. => ok for core components of the form. To be done for inputs, not sure of the best strategy, because that means needing a huge refactor. We could provide those values both through props and let the user also use the context?
  • Provide doc on how to create your own components
  • We need to figure out fragment registering. Some parts of the Form are relying on "expandQueryFragments" from "packages/vulcan-lib/lib/modules/fragments.js". What to do with those?
  • Get "Locales" from context in packages/react-components/components/form/FormIntl.tsx
  • Correct props for the context that provides the components
  • Correct input props, whitelist correctly when using an HTML input => started the work, but we still to correctly differentiate "HTML INPUT" and "HTML SELECT" correctly
  • Figure out "beforeComponent" / "afterComponent", and "instantiateComponent" pattern
  • Reintroduce existing unit tests => we could use this as an occasion to use the new storybook testing plugin, to load the form from stories directly? So that we don't duplicate decorators and all
  • Implement the SmartForm, that queries actual graphql (=FormContainer.tsx)
  • Fix Storybook Testing React misconfiguration storybookjs/testing-react#27
  • Fix issue with circular dependency: in packages/react-ui/components/form/tests/Form.stories.tsx, when importing ./Form, we have a circular dependency with packages/react-ui/components/form/defaultVulcanComponents.ts, not sure how to fix it... Currently we hard copy the Form folder to Form2 to duplicate the components but that's not very good.... => solution is to split the context provider with default component from the context consumer with the hooks, this avoids having an explicit circular dependency
  • Figure how to set the context => Story ok, nested dependencies seems to work ok
  • How to handle depencies between components that belongs the same context? => OK
  • Handle getLabel context, eg in packages/react-components/components/form/FormError.tsx
  • Switch to TS
  • Switch to stateless => done
  • Finish the FormLeavingManager component to handle the warnOnUnsavecChanges feature (we need to figure what is the history object exactly + stop triggering the message on page leave. We might need to recode the history.block feature?
    vercel/next.js#12348
    https://github.com/ReactTraining/history/blob/master/docs/blocking-transitions.md
    vercel/next.js#5377
    vercel/next.js#2476 (comment)
    vercel/next.js#2694
    => ok for browser events. See #40 for SPA specific routing, this belongs to Vulcan Next

image

Step 2: improve the API: better naming, clearer components structure etc.

  • Move graphql logic of the success callbacks out of Form, and put it into FormContainer
  • Introduce an intermediate FormGenerator between SmartForm and Form => FormGenerator would generate the whole form while Form would focus on form management. It would allow to have 3 behaviour:
  1. form + auto generation + graphql (this is currently the SmartForm
  2. form + autogeneration without graphql (this is the Form currently)
  3. just the form and custom inputs (this is not possible yet)

Others

About custom components

It is already possible to use a custom component as input:

{
   type: String,
   input: MySuperReactInput
}

Limitation is that you cannot override this component using the context, it's hard written in your schema.

We could allow something like this:

{ 
   type: String,
   input: "MyCustomInput"
}

The advantage of this pattern is that you can change "MyCustomInput" from the context, as other Vulcan inputs. It can be interesting if you want to implement an API that allows some component swapping or programming a multitenant app.
You basically can create your own Vulcan app.

Limitation is that this pattern, which already exists in Vulcan Meteor, tends to be deadly over-used: since the reference to the component is implicit, you may unknowingly create circular dependency, reference a component that doesn't exist etc.
So I am not in a hurry to add this feature. This might need another ticket.

Reintroduce views/terms/parameters system from old Vulcan Meteor

Is your feature request related to a problem? Please describe.

useMulti is now using a client-side filtering system, but sometimes you might want server-side terms.

Describe the solution you'd like
Reintroduce old "terms" system, but only for advanced filters this time.

Describe alternatives you've considered

  • custom mutation (complex)
  • client-side filter (but more limited)

Additional context
Add any other context or screenshots about the feature request here.

Clean handling of several versions of React - yarn link of node modules with Lerna

This concerns the development environment, when one want to test the local version of Vulcan NPM in a Vulcan app.

facebook/react#13991
facebook/react#14257
https://stackoverflow.com/questions/34706817/how-prevent-multiple-copies-of-react-from-loading
https://next.material-ui.com/getting-started/faq/#duplicated-module-in-node-modules
https://robkendal.co.uk/blog/2019-12-22-solving-react-hooks-invalid-hook-call-warning

Using npm link or yarn link on Lerna packages ends up loading 2 versions of React and React Dom. We might avoid this by making React and React DOM peer dependencies, or using more robust commands in Vulcan Next

Current fix:

  • In Vulcan NPM, run cd node_modules/react && yarn link, same with react-dom or any problematic duplicate package
  • In your project, run yarn link react, yarn link react-dom, etc. with any problematic library. This way the Vulcan NPM version will be preferred

Alternative: using Webpack but that's not good.

Possible solution:
Allow to pass a VULCAN_PACKAGE_DIRS env variable like we did in Meteor and handle yarn link ourselves, so the end user doesn't need to know it.

Remove import React

Like we do in Next, we shouldn't need to explicitely import React in any .tsx file

Material-ui styles is duplicated

Probably related to #5

It looks like there are several instances of `@material-ui/styles` initialized in this application.
This may cause theme propagation issues, broken class names, specificity issues, and makes your application bigger without a good reason.

See https://material-ui.com/r/styles-instance-warning for more info.

We need to use the version from vulcan-npm most probably.

Activate strictNullCheck in TS

Is your feature request related to a problem? Please describe.
strictNullCheck improves code quality a lot, and you don't want to activate it late in the project because it may require a lot of change

Describe the solution you'd like
Activate and fix the issues

Introduce SmartSearch or SmartFilter

Is your feature request related to a problem? Please describe.

Similar to a SmartForm, but instead of creating/editing, specialized for searching and filtering.

Describe the solution you'd like

The schema would need a few more fields to define the possible values for some types (eg for a range, define min and max you want to see.
They could also be computed by an async callback (if you want to compute a $distinct, a min/max for a dynamic range).

Helper for virtual relations

Is your feature request related to a problem? Please describe.
Imagine you have Todo and User models. A User can have multiple Todos. A Todo can have only one User, its owner.

To create a relationship from Todo to User, you need to a have a field Todo.userId. You then use the hasOne helper to create a resolver able to fetch User related to a Todo.

But if you want the reverse, you have 2 patterns:

1. Duplicating ids

You add a field User.todoIds. You use hasMany on this field. Limit: it is quite difficult to keep both data in sync. You have to keep track of the Todo userId but also update the User todoIds on each mutation.

We technically are already able to code this with hasOne hasMany and callbacks.

2. Resolver only

You add User.todos and create a resolver that can fetch the todos based on Todo.userId. Limit: it is more costly, as you need to find all users todos among all possible todos. You probably need to set an index on Todo.userId in your Mongo db to make this efficient.

For this pattern, we currently don't have any helper.

Describe the solution you'd like

A way to define hasOne or hasMany relationship WITHOUT creating a new field containing ids, in order to keep a single source of truth.

Note: even if we use Mongo, those are relational patterns. We don't really use the NoSQL approach of Mongo in Vulcan, so that's fine to use patterns you would find traditionally in SQL DBMS.

In Hasura, you can select the reference schema of a relationship. It can be either the current schema, which is equivalent to pattern "1.". But it can also be a different schema, which is equivalent to pattern "2.".

image

Describe alternatives you've considered

For pattern 1., the current syntax is fine, but you need to define the callbacks carefully. We can keep it as is.

For pattern 2., we could provide some relation syntax.
Multiple syntaxes are possible, for example:

todos: {
  ...
  relation: {
    model: Todo, // model where to look for data
    from: "_id", // field in the current schema to compare
    to: "userId", // field in the reference schema 
    kind: 'hasMany' 
  }

The generated resolver will simply run a query on Todo collection to find relevant data.

We could also use the normal resolveAs syntax but provide an helper to create the resolver:

todos: {
   resolveAs: {
       resolver: arrayRelationResolver({from: "_id", to: "userId", targetModel: Todo})

Faster build with SWC or Esbuild

Is your feature request related to a problem? Please describe.
I am exploring modern alternatives to Babel/Webpack for a faster build time.

First experiment is on the react-ui package.

Describe the solution you'd like
- Need to be able to output code for Node (during SSR) and the browser (for React components)
- Need to generate .d.ts?

  • Esbuild and SWC cannot do that, and won't do that, because that's a slow heavy operation.
    However, we could generate those files only when publishing the package on NPM, or by hand. Sadly, I failed to find the right command with tsc or ts-loader. See swc-project/swc#657

Describe alternatives you've considered

SWC

Next.js is backing SWC, so it's expected to have a lot of updates.

Be careful that SWC is a parser, so a replacement to Babel, not to Webpack. They are building "SWCpack" for this purpose but it's not there yet.

Dumping Babel may have some consequences, see vercel/next.js#30174, as SWC doesn't support all packages yet and have a smaller ecosystem.

It proposes a Webpack loader => great for fast setup by replacing babel-loader.

We need to figure the .d.ts generation thing.

Esbuild

Seems to work ok, very fast. But since Next is preferring SWC, that is written in Rust (Esbuild is in Go, so great language but less relevant for a build tool), we might go the Next.js way.

Not completely sure how it replace Babel/Webpack. But I am sure it doesn't correctly support .d.ts either: evanw/esbuild#95

Additional context
Add any other context or screenshots about the feature request here.

Handle programmatic users/API keys for 3rd party

Is your feature request related to a problem? Please describe.
We currently have an API key pattern in Vulcan, see "context" creation for instance. It grants admin rights to the API user => it is only meant to be used for services owned by the same company. You can't give an API key to a partner company for example, as it lacks finer permissions.

Describe the solution you'd like
A new "apis" groups for basic permission handling?

But most importantly, clean "signup"/authentication process for server to server communication.

As a first step, we could simply reintroduce API keys into Vulcan Next/Vulcan NPM based on Vulcan Meteor current implementation.

Check correct dependencies between local packages in Lerna

Is your feature request related to a problem? Please describe.
When developping Vulcan NPM, all local packages are known by other packages.
It makes it difficult to detect when there is a missing dependency. For instance, say that @vulcanjs/graphql depends on @vulcanjs/i18n. During development, you won't have any problem, even if you forget to add the dependency to @vulcanjs/i18n in @vulcanjs/graphql package.json.

Describe the solution you'd like
Find a way to detect missing dependencies as well as unused dependencies in a Lerna mono repo.

@vulcan/mdx recursive list mdx files

In order to make ListMdxFiles recursive, we should decide how we handle the index.md files, or if we handle them.

For example, /docs/nest/index.md should resolve to /docs/nest instead of /docs/nest/index.

This was the object planned... if I'm correct.

interface MdxFile {
  fileName: string; // filename with extension
  relativePath: string;
  pageName: String
}

RFC: Merging all repos into one

Describe the problem you want to solve or enhancement you want to bring

Development of Vulcan NPM can become tricky when you want to experiment it in a real app, like in Vulcan Next. We need to use Yalc etc.

Having only one repo containing Vulcan core logic (NPM) + demo apps (Next, Express, Gatsby...) makes it easier to develop the framework.

Cons: can't the monorepo becomes a mess of complicated apps?
Define relevant concepts

Blitz is structured as a monorepo but revolves a lot around the Next app. We'd want to do the reverse: putting Vulcan Next within the monorepo.

Describe your solution
We could have scripts to create apps, based on templates => npx create-vulcan-app next generate a Next app, a Gatsby app, etc.

We could also keep separate repos like Vulcan Next, but generate them automatically. So that developers can still simply clone Vulcan Next for instance and get the latest code.

Questions to the community

  • What's your opinion?
  • Are you ok with using commands to create the app like npx create-vulcan-app next?
  • What would be your approach? Eg how do we setup the repo so you can do something like npx create-vulcan-app or npx vulcan create next?
  • What would be your preferred syntax and behaviour?
  • How can we handle folders that are app and folders that are packages within the Lerna monorepo? Install must not become a mess and build-time must not explode, so the app have to be kept kinda separate of Lerna.
  • How do we plug the local packages? So that when we develop, the app automatically use the local versions of @VulcanJS packages
  • If you want to contribute: try to setup a demo, for instance a very very simple Express app within the vulcan-npm monorepo.

Redundancy between mutators and defaultMutations?

Describe the bug
We have a function to get the document to update in default mutations, but also in the mutators:

  • packages/graphql/server/resolvers/defaultMutationResolvers.ts (getMutationDocument)
  • packages/graphql/server/resolvers/mutators.ts

Shouldn't we remove the one in the default mutation?

Clarify dependency between "model" and implementations (graphql, mongo, apollo...)

We need to avoid circular dependencies between NPM packages.

  1. make the "model" unaware of graphql and mongo, it just knows its schema. Then, when we create the model, we give the possibility to enhance it, eg with graphql and mongo. That's probably the cleanest, but does that make sense ? A Vulcan model will never live without a persistence solution or even without graphql, in a foreseable future

  2. I put the model related things, like graphql fragment generation or mongoose schema generation, in a single NPM package. So for instance "@vulcan/graphql" is just a bunch of helpers unaware of Vulcan, and "@vulcan/model/graphq" contains the logic Currently that makes sense, because those concepts are actually deeply tied. But that's not the best solution for the future.

2 is convenient and easier to implement.
1 is however the best architecture, so that we can swap technologies. This means adding potential extensions to the model.

Edit: I've tried second solution, with graphql being an extension of the Model. I'd like the final type (raw VulcanModel, VulcanGraphqlModel etc.) to be inferred from the included extensions, but not sure it's doable in TypeScript.

Unify syntax between queries and mutation

Current state:

  • single will return { data: { foo : { result: <your-foo>}, result: }}`
  • multi will return { data: { foo : { results: <your-foo>}, results: }}`
  • create will return { data: { foo : { data: <your-foo>}, document: }}`
  • same for all other mutations

Proposal

  • single will return { data: { foo : { document: <your-foo>}, document: }}`
  • multi will return { data: { foo : { documents: <your-foo>}, documents: }}`
  • create will return { data: { foo : { document: <your-foo>}, document: }}`
  • same for all other mutations

This way we use document as the canonical name for the object we are modifying ; we remove the confusing result name, and we use data only for generic data (eg the response of apollo query that contains both documents and metadata)

Steps

  • Unify the shortcut prop that contains the document (added client-side by the hook)
  • Unify in Vulcan backend (defined server side in each resolver)

Listen to route changes in a SPA without tight coupling framework specific router implementation

Is your feature request related to a problem? Please describe.

In the SmartForm, we want to display a blocking popup "You have unsaved changes, are you sure you want to leave" on various events: closing the browser, changing route.

The problem is that "changing" route can happen at 2 levels:

  • in the browser. Easy to handle
  • in the SPA. That's where trouble begins.

The problem is that SPA route transition are handled by a router, which is framework specific. React Router was suffering from this limitation, and Next also suffers from it: you have to use a framework specific solution to block the route transitions.

Describe the solution you'd like

  • A pattern to tell any SPA router that we want to block route transition FIRST DRAFT OK, it triggers an event. It's app/framework responsibility to listen for those block/unblock events and add relevant behaviour for their own JS router.

For instance in React Router, that means calling history.block(), in Next, adding a listener to "beforepopstate"

Demo of a generic hook: https://gist.github.com/eric-burel/75301e2ba01967e0ec909ddf7fae5100
Work ongoing there: #31

  • A sample implemention with Next, in Vulcan Next TO BE DONE

Describe alternatives you've considered
It's possible to have Next specific solution but that creates a tight coupling which we refuse

Additional context

Create queriers in addition to mutators + standardize mutators

Is your feature request related to a problem? Please describe.

  • Connector is a more direct connection to the db, but it doesn't use Vulcan "magic" and callbacks. They are internal to Vulcan and should not be used in user apps.

  • Mongoose models are ok for direct connection to the db, it's ok to use them but also won't run the callbacks.

  • Mutators will run Vulcan callbacks and permission checks, they are better suited for custom mutations.
    We need an equivalent for queries, "Queriers". In particular, the querier should run permission check and remove unallowed fields.

Describe the solution you'd like

  • It should be as easy as possible to use a mutator. Namely, mutators can be either called in a mutation, in response to a request, or outside a mutation, so without a request (eg when seeding data) => both use cases should be handled correctly.

Normally, mutators do not depend on the request context, and should not use DataSources (they are only needed for field query resolvers, not for mutations), so the graphql context should not be a problem. It used to be an issue in Vulcan Meteor but that was because we used the GraphQL context wrongly.

  • Queriers are the equivalent for queries. They should fetch data correctly, and check permissions namely.

Updating user form fields in Smart Form

Describe the bug
Updating user form fields gives the following error:
Cannot query field "userId" on type "User". Did you mean "username"?, Location: line 14, col 3, Path: undefined

To make it work, you have to manually add userId to your User schema.

The default fragment somehow systematically includes userId but User is an exception: it doesn't have an userId in its schema

To Reproduce
Steps to reproduce the behavior:

  1. Create a SmartForm for updating user profile fields based on meteor-demo.tsx, using useUpdate({model: User}).
  2. When you press update, you get the above error

Expected behavior
The form should successfully update user fields without any modification to the user schema


Here is my demo form that was not working:

import {
  useUpdate,
} from "@vulcanjs/react-hooks";
import { User } from "../../models/meteorUser";
const ProfileForm = (props) => {
    const [updateDocument] = useUpdate({ model: User });
    if(props.user){
      return (
        <form
          onSubmit={async (evt) => {
            evt.preventDefault();
            evt.stopPropagation();
            const displayName = (evt.target as any).displayName.value;
            // const url = (evt.target as any).url.value;
            await updateDocument({
              input: { id: props.user._id, data: { displayName } },
            });
          }}
        >
          <input
            placeholder="name"
            type="text"
            name="displayName"
            defaultValue={props.user.displayName}
          />
          <button type="submit">Update</button>
        </form>
      );
    }else{
      return null
    }
  };
  export default ProfileForm

Avoid client-side leaks of resolvers code

Is your feature request related to a problem? Please describe.
2 kinds of leaks:

  • calling default resolvers when creating the model => we should do that automatically
  • adding field resolvers to a schema
  • adding api only fields with a field resolvers

Describe the solution you'd like

// myModel.ts
// We could still use declarative resolvers here, like hasMany relationships, since they don't leak code
export const MyModel = createGraphqlModel({...}) // type: VulcanGraphqlModel

// myModel.server.ts
// Put field resolvers here so they are server only
interface FieldServerSchema extends VulcanSchema {
   resolver: ...
}
interface VulcanGraphqlServerModel extends VulcanGraphqlModel {
    ...
}
export const MyModel = extendGraphqlModel({ ... }) // type: VulcanGraphqlServerModel

// index.ts
// the index would export the main model => no risk of leaking server code.
export * from "./myModel"

Then it's user responsibility to import "myModel.server" instead of "myModel" in /api/graphql (we could provide an exemple in Vulcan next.

Main difficulty is to merge everything correctly and get the right typing everywhere to differentiate where we can accept both kind of model and where we accept only common model.

Note: we could also provide a createGraphqlServerModel, for contexts where you want to create directly a server schema (eg in future "Vulcan Express" where you have only a server and no client).

RFC: Fullstack and isomorphic package (work in progress)

Describe the problem you want to solve or enhancement you want to bring

Ability to write full-stack "feature-oriented" packages with both server and client features, in NPM, as we are used to do in Meteor.

@vulcanjs/graphql is a good example of this, as it generates the graphql backend, the client fragments, and both are very tied.

Define relevant concepts

Full-stack package: A package that export server-specific code and client-specific code. But it could apply to any package that expose different code depending on various environment (server, mobile, client).

Meteor packages are example of fullstack package with their client and server logic.

This is different from just having code that works everywhere, like a low-level library. We are talking about code that is actually specific to the env (calling window on the client for instance).

We should be able to do something like import createServer from "@vulcanjs/graphql/server", import someHook from "@vulcanjs/graphql/client"and import someSharedCode from "@vulcanjs/graphql"

Isomorphic import: Using a fullstack package transparently.

Example: a full-stack package could expose @vulcanjs/graphql/server and @vulcanjs/graphql/client. An isomorphic import would be import foobar from "@vulcanjs/graphql" and let the the build system add /client or /server depending on the context.

Meteor is also doing that.

Limitation: the text editor can't know the environment where the code is run. So, for static typing, you have to explose a generic version. Meteor is subject to this limitation.
Blitz.js has something similar, but with "unbalanced isomorphism", limited to how they consume the server using a custom query system. The typing is coming from the server, that contains the actual logic, as the client version is just a dull wrapper that does an HTTP call.

True isomorphism: you write a single piece of code and let the build system modify it depending on the execution environment. Next pages fall into this category. getServerSideProps and getStaticProps functions exported in a page are server-only methods. The rest React (so client but also compatible with server-side rendering). At build time, those methods are removed from the client bundle.

This is something we would want to achieve for Vulcan model's:

const Foo = createModel({
   schema: {
      foobar: {
         type: string; // both client and server, to render form, generate the gql schema etc.
         resolver : () => {...} // field resolver, server-only
         onCreate: () => {...} // server-only
      }
   },
  queries: { ... },
  mutations:  { ... } // CRUD operations, server-only
})     

In this example, the schema mixes shared elements, like the type for "Foo.foobar", and server only methods like the graphql resolver.
The power of Vulcan is to let you write only one schema with everything. The limitation is that you don't want the client bundle to contain server code, and sometimes vice-versa. You can bypass this with ugly require or playing around with GraphQL context, but only so much.

Related

  • RFC for Vulcan Package Format usable on any any app. It is focused on the actual content of the package, the what, (how we export graphql stuff etc.), while this RFC is focused on how we deliver such fullstack package, regarding build system, import tec. VulcanJS/vulcan-next#9
  • Random thoughts about moving from Meteor to Next from back when we started the migration, with a few things related to packages: VulcanJS/vulcan-next#1
  • Meteor application structure: https://guide.meteor.com/structure.html
  • Next pages
  • Blitz.js query system
  • Blitz architecture discussion: blitz-js/blitz#1141

Describe your solution

First step is implementing full-stack packages.

Then isomorphic import could be kept as a "magic" helper specific to Vulcan. But it is not mandatory (like if you create an Express server and don't use weback, you just do import foobar from "@vulcanjs/graphql/server" as usual). This feature is more comfort for people used to Meteor.

Questions to the community:

For full-stack package: Multi entry Webpack export/TypeScript. It should not be too hard, but I've never done that before, there are a lot of moving pieces. So help welcome!

For isomorphic import: there is a demo package multi-env that does exactly this. The way it works, it imports either index.server or index.client, using Webpack magic.

For the text editor autocompletion, or common code, we can also add an index that exports both. That's a prototype so again help and feedback welcome.

A good example to get started is the graphql package, that contains both server and client logic.

For true isomorphism: it's not easy at all to mix server and client stuff at the same place.

  • is it even a good idea? MVC architecture would basically force you to explode your model in multiple pieces and bypass this issue.
  • it could be easier to have multiple named exports with a convention, like Next does.
    For example, in foobar.ts:
export const schema = { foobar: {...} } // the common part
export const fieldResolvers = {foobar: {...}} // server-only. We have to repeat the field names
export const resolvers = {... } // server-only
// etc.

And then you create the full model using some build time magic. This is still a bit blurry, that's the part that require the most thinking.

Publish Next packages

Building packages meant specifically for Next is specific.
We probably don't need to have a full build process, and we should instead let Next do it's magic as much as possible, directly within the Next user app.
Exactly like a Meteor package does not necessarily need a lot of transpiling to be published.

See this discussion for example, regarding process.env in NPM packages: vercel/next.js#17262

I did a test with next-transpile-modules but did not manage to make it work as expected yet: martpie/next-transpile-modules#120

At the moment, Next packages specific to vulcan are shipped directly with the starter, they are not real NPM packages.

Add a test/protection to check that NPM packages are not empty

Is your feature request related to a problem? Please describe.
Unit test may let pass the case where we forget to correctly export files in the NPM package.

We absolutely need to run yarn run build locally before publishing packages, so we need a way to secure this process.

Describe the solution you'd like
We could have multiple strategies, like running npm pack --dry-run.

We can also add a publish + prepublish script that forces the build, but that can be annoying when you have already built the app and want to skip this step. I'd rather check if the build has been done, than systematically building.

To reproduce
Run yarn clean then npm pack --dry-run in a package => you will have an empty result because /dist folders are empty.
We could also simply check that all packages contain a "dist" folder or something similar.

RFC: Magical replacement of components

Cross post of VulcanJS/Vulcan#2549

Dream goal

Goal is to replace the dynamic Component replacing pattern by explicit exports that would "magically" replace components.

Imagine a component structured like this:

// datatable.js
const DatatableHeader = () => (<header>default header</header>)

const DatatableFooter = () => (<footer>default footer</footerr>)

const DatatableContent = ({children}) => (
    <div>
        <DatableaHeader />
            {children}
        <DatatableFooter />
</div>

const Datatable = () => (
    <div>
        <DatatableContent />
    </div>
)

You may want to replace the Header, and the Content with your own components:

// my-overrides.js, a special file that contains my Vulcan components override. Could be detected at build time.
export * from "vulcan/ui-react" // magically imports DatatableFooter, DatatableHeader, DatatableContent and Datatable
export const DatatableHeader = () => (<header> I AM OVERRIDEN </header>) // first override
export const DatatableContent = () => (<div><DatatableHeader/> <div> Hello </div> <DatatableFooter /></div> // second override

Then you consume them like this, business as usual:

// my-page.js
import { Datatable } from "vulcan/ui-react" // import default components + my custom override

export const MyPage = () => (<Datatable>Hello!</Datatable>) // DatatableContent has magically been replaced in Datatable

It's probably undoable with Meteor, but it could prove doable with Webpack.
The tricky part is that you want to override only certain component, that are themselves nested into other replaceable component. You get a kind of recursive/circular import issue.

What's doable today

This pattern is already implementable with one level of imports like this:

// my-ui.js
export * from "vulcan/react"
export const DatatableHeader = () => (...) // will reexport, overriding default DatatableHeader from "vulcan/react"

but it's not enough to handle more deeply nested component replacement, for complex components like DataTable or SmartForm that have multiple layers of replaceable component.

Possible implementation

Step 1: detecting "magic" replacement

First step is defining a syntax for magic components.

Possibilities:

  • Prefixing components, eg <@Datatable>: can be detected component by component but it's not very clean

  • "Magic import syntax", eg import { ComponentToReplace } from "vulcan/magic-component"/ export * from "vulcan/magic-components": detect that we want to import "magic components" or override them

  • a special file name for files that exports component override

  • explicitely declaring dependencies between components eg export { component: Datatable, dependsOnMagic: ["DatatableContents"] }

Step 2: build

The difficulty is to handle deeply nested replaceable component.

  • Datatable is defined with default components
  • We detect that DatatableFooter is overridden by user
  • We rebuild DatatableContents with overridden DatatableFooter export
  • We rebuild Datatable, which depends on DatatableContents which contains an overridden export.

The build would probably look like a recursive structure or a tree, where we rebuild everything until all nested components are replaced by user's overrides.

Add better source maps

Is your feature request related to a problem? Please describe.
Source maps are not displayed when using the package, eg useMulti.
Ideally, they should provide better information when there is an error.

Describe the solution you'd like
Investigate if we should or should not add source maps to the packages, and why they don't show up already with inline production source maps.

Can't test the devel branch

While trying to run tests on the devel branch, you can see that jest find two projects with the same config path.

Steps to reproduce the behavior:

  • Pull the devel branch
  • Try "yarn test"

image

  • OS: Windows 10

Investigate mongo projections

Is your feature request related to a problem? Please describe.
Our connectors are not very smart about the field they query
Describe the solution you'd like
Adding an optional "projection" parameter to connector, for find, findOne...

Describe alternatives you've considered
Mongo/Mongoose often takes the projection as the second parameter: selector, then projection, then options (sort, limit).

In Vulcan the order would be slightly different: selector, options, projection. This makes more sense because projection is less used in custom resolvers.

Add changePassword hook for meteor legacy

Is your feature request related to a problem? Please describe.
image

Describe the solution you'd like
Ad hook here + relevant mutation in Vulcan
Describe alternatives you've considered
We already have setPassword, but setPassword do not recheck user's password.

Handle slugs

Is your feature request related to a problem? Please describe.
See existing slugs related feature in Vulcan Meteor
(slug = human readable unique id, useful for clean urls for instance for articles)

Use of TData might be wrong

It's not clear in Apollo whether the generic type "TData" refers to the type of "document" or type of the response.
For example, if it refers to the document, we could have TData = { hello: string }, so it matches the schema. If it refers to the data, we could have TData = { data: { createFoo : { document: { hello: string }}, so TData would vary amongs mutations.

The second case is more likely in Apollo. But what we want in Vulcan is the first case. We should introduce a new TModel generic which is the type of the model, and try to build the corresponding "TData" automatically.

The biggest issue is that the name of the resolver is dependent on the model (createFoo, createBar), it is not static so hard to precompute in typescript.

Graphql schema generation code, NPM version

Work in progress in branch feature/resolvers

Prototype

  • Move Vulcan code
  • Generate string typedefs
  • Refactor
  • Generate default resolvers
  • Handle collection permissions and fields permissions
  • Plug in an existing Apollo server => POC ok
  • Have a cleaner pattern to tell when to run "restrictViewableFields" in mutators.ts or not (for server side programmatic calls, we might want to avoid this call). In the past we would check "context.Users". => we nowuse asAdmin option to bypass restrictions
  • In validation.ts, check that the call to new SimpleSchema(schema) works when there are nested fields to, or if we must call it for each field also => done, in schema we must remove calls to new SimpleSchema
  • Reenable upsert mutator, weird way of triggering the other mutations => it is now removed

MVP

  • Reenable callbacks in mutators and queries, need to be able to pass callbacks in the model
  • Provide a Mongo Connector, with options
  • Check that relations are working as expected => ongoing work, fixed the schema generation already, need to test the resolver implementation too
  • Provide a way to add "currentUser" to context => ongoing work in Vulcan Next, already setup passport, need to add the currentUser if that's not yet the case
  • Make calls to get default resolvers optional
  • Check that field resolvers are working as expected => ongoing work for relation, to be tested for resolveAs
  • Reenable customSort
  • Provide helpers to build the graphql context for a list of models => ongoing work
  • Support legacy mutations regarding to connector API change? (removal of data loader etc.)
  • Investigate data loaders/connector for graphql (as we need to cache the queries made to the same collection during a graphql request)

Testing

  • Investigate throwError function
  • Write e2e or unit tests for all query and mutation resolvers
  • Reenable tests for the multi resolver or improve them (currently too hard to write). Important for security reasons.

Later/discussions/improvements

  • Do not generate Input/DataInput for mutations that do not exist for a given model
  • Upsert? (I don't like it...)
  • How to integrate with "User" related schema, resolvers etc.-
  • Reenable default "input" in collection for sorting and filtering
  • Reenable customServer

Branch: feature/vulcan-server

"Jest" debugger broken

Describe the bug
I can't find a way to have breakpoints to work
To Reproduce
Set a breakpoint ina component and run "Jest current file" debugger => your breakpoint is not reckognized, with either "source map problem" or "breakpoint set but not yet bound message".

Expected behavior
Debugger is able to find the source maps and breakpoints.

Additional context

The TS Webpack setup is tricky:

  • I want to generate packages when I build the app
  • Source maps inline do not seem to appear? I've tried to switch to file sourcemap in the webpack config with no luck
  • Only "./index.ts" is included in the build, since that's the only exposed files => it's the only file with a .js version
  • I am not sure how to debug this

I wonder if VS code expects one source map per file, which would be weird. Maybe it fails to detect that "index.js" is the built file and that it's where it should look (and instead fetch Form.js or Form.js.map which doesn't exist).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.