V0.37 Release-Candidate is Available: Feedback Wanted

Redwood’s upcoming version v0.37 is going to knock your proverbial socks off :socks: And it’s available now as a release candidate for you to try out!

GraphQL gets Rad

Thanks to collaboration with The Guild and amazing work by @dthyresson and @danny, the GraphQL Server is getting first class treatment with amazing build-in defaults and directives-based security-by-default. Maybe someone will show me otherwise, but Redwood now offers a best-in-class GraphQL integration that’s never been achieved in a framework before. :trophy: (Am I correct, @dotansimha and @n1ru4l?)

Generators get Customization

But that’s not all. There’s also custom generator templates (thanks to @rob) that we know you’re going to love.

So this one is going to be jam-packed. And we need your help improving the release candidate before we publish the final version!

We need your help

This version has a complex upgrade path. There’s an automated code-modification PR in the works, but before that’s ready we need your help:

  • verifying the upgrade process
  • testing the new features and changes on existing applications
  • making the whole thing even better

How to try the latest Release Candidate

For reference, here is the latest Release-Candidate Changelog:

Everything else you need to know, including how to use yarn rw upgrade --tag rc is in this document:

Draft Documentation for new features

How to use Redwood Directives

How to create and use Generator Templates

Feedback wanted

Once you have a chance to go through the upgrade, please let us know how it went and any suggestions for improvement.

If you have questions or need support along the way, just reply to this thread or ask for help via the Redwood Discord.

And thank you! Redwood v0.37 is going to be even better because of this amazing community of contributors. :rocket:

7 Likes

Ran into this error trying to upgrade to 0.37

Uncaught Invariant Violation: No Apollo Client instance can be found. Please ensure that you have called `ApolloProvider` higher up in your tree.
    at new InvariantError (http://localhost:8910/static/js/app.bundle.js:97615:28)
    at invariant (http://localhost:8910/static/js/app.bundle.js:97627:15)
    at useApolloClient (http://localhost:8910/static/js/app.bundle.js:59:85)
    at LandingNav (http://localhost:8910/static/js/app.bundle.js:31210:79)
    at renderWithHooks (http://localhost:8910/static/js/app.bundle.js:76443:18)
    at mountIndeterminateComponent (http://localhost:8910/static/js/app.bundle.js:79269:13)
    at beginWork (http://localhost:8910/static/js/app.bundle.js:80507:16)
    at HTMLUnknownElement.callCallback (http://localhost:8910/static/js/app.bundle.js:65403:14)
    at Object.invokeGuardedCallbackDev (http://localhost:8910/static/js/app.bundle.js:65452:16)
    at invokeGuardedCallback (http://localhost:8910/static/js/app.bundle.js:65514:31)
InvariantError @ invariant.esm.js:12
invariant @ invariant.esm.js:24
useApolloClient @ useApolloClient.js:6
LandingNav @ LandingNav.js:23
renderWithHooks @ react-dom.development.js:14985
mountIndeterminateComponent @ react-dom.development.js:17811
beginWork @ react-dom.development.js:19049
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
beginWork$1 @ react-dom.development.js:23964
performUnitOfWork @ react-dom.development.js:22776
workLoopSync @ react-dom.development.js:22707
renderRootSync @ react-dom.development.js:22670
performSyncWorkOnRoot @ react-dom.development.js:22293
(anonymous) @ react-dom.development.js:11327
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
flushSyncCallbackQueueImpl @ react-dom.development.js:11322
flushSyncCallbackQueue @ react-dom.development.js:11309
scheduleUpdateOnFiber @ react-dom.development.js:21893
enqueueSetState @ react-dom.development.js:12467
push.../node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:365
PageLoader.startPageLoadTransition @ page-loader.js:92
async function (async)
PageLoader.startPageLoadTransition @ page-loader.js:89
componentDidMount @ page-loader.js:116
commitLifeCycles @ react-dom.development.js:20663
commitLayoutEffects @ react-dom.development.js:23426
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
commitRootImpl @ react-dom.development.js:23151
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
commitRoot @ react-dom.development.js:22990
performSyncWorkOnRoot @ react-dom.development.js:22329
scheduleUpdateOnFiber @ react-dom.development.js:21881
updateContainer @ react-dom.development.js:25482
(anonymous) @ react-dom.development.js:26021
unbatchedUpdates @ react-dom.development.js:22431
legacyRenderSubtreeIntoContainer @ react-dom.development.js:26020
render @ react-dom.development.js:26103
../node_modules/@redwoodjs/web/dist/entry/index.js @ index.js:24
options.factory @ react refresh:6
__webpack_require__ @ bootstrap:24
__webpack_exec__ @ valueFromASTUntyped.mjs:56
(anonymous) @ valueFromASTUntyped.mjs:56
webpackJsonpCallback @ jsonp chunk loading:560
(anonymous) @ app.bundle.js:1
react-dom.development.js:20085 The above error occurred in the <LandingNav> component:

    at LandingNav (http://localhost:8910/static/js/app.bundle.js:31205:74)
    at div
    at PageLayout (http://localhost:8910/static/js/app.bundle.js:32005:23)
    at HomePage
    at PageLoader (http://localhost:8910/static/js/app.bundle.js:14020:5)
    at InternalRoute (http://localhost:8910/static/js/app.bundle.js:14487:3)
    at Route
    at ParamsProvider (http://localhost:8910/static/js/app.bundle.js:14214:3)
    at RouterContextProvider (http://localhost:8910/static/js/app.bundle.js:14390:12)
    at LocationAwareRouter (http://localhost:8910/static/js/app.bundle.js:14562:3)
    at LocationProvider (http://localhost:8910/static/js/app.bundle.js:13873:5)
    at Router (http://localhost:8910/static/js/app.bundle.js:14546:3)
    at Routes
    at GraphQLHooksProvider (http://localhost:8910/static/js/app.bundle.js:15572:3)
    at ApolloProvider (http://localhost:8910/static/js/app.bundle.js:21563:21)
    at ApolloProviderWithFetchConfig (http://localhost:8910/static/js/app.bundle.js:15277:3)
    at FetchConfigProvider (http://localhost:8910/static/js/app.bundle.js:15492:3)
    at RedwoodApolloProvider (http://localhost:8910/static/js/app.bundle.js:15338:3)
    at r (http://localhost:8910/static/js/app.bundle.js:87929:6650)
    at RedwoodProvider (http://localhost:8910/static/js/app.bundle.js:15731:3)
    at FatalErrorBoundary (http://localhost:8910/static/js/app.bundle.js:15421:5)
    at App

React will try to recreate this component tree from scratch using the error boundary you provided, FatalErrorBoundary.
logCapturedError @ react-dom.development.js:20085
update.payload @ react-dom.development.js:20133
getStateFromUpdate @ react-dom.development.js:12102
processUpdateQueue @ react-dom.development.js:12250
updateClassInstance @ react-dom.development.js:13013
updateClassComponent @ react-dom.development.js:17432
beginWork @ react-dom.development.js:19073
beginWork$1 @ react-dom.development.js:23940
performUnitOfWork @ react-dom.development.js:22776
workLoopSync @ react-dom.development.js:22707
renderRootSync @ react-dom.development.js:22670
performSyncWorkOnRoot @ react-dom.development.js:22293
(anonymous) @ react-dom.development.js:11327
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
flushSyncCallbackQueueImpl @ react-dom.development.js:11322
flushSyncCallbackQueue @ react-dom.development.js:11309
scheduleUpdateOnFiber @ react-dom.development.js:21893
enqueueSetState @ react-dom.development.js:12467
push.../node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:365
PageLoader.startPageLoadTransition @ page-loader.js:92
async function (async)
PageLoader.startPageLoadTransition @ page-loader.js:89
componentDidMount @ page-loader.js:116
commitLifeCycles @ react-dom.development.js:20663
commitLayoutEffects @ react-dom.development.js:23426
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
commitRootImpl @ react-dom.development.js:23151
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
commitRoot @ react-dom.development.js:22990
performSyncWorkOnRoot @ react-dom.development.js:22329
scheduleUpdateOnFiber @ react-dom.development.js:21881
updateContainer @ react-dom.development.js:25482
(anonymous) @ react-dom.development.js:26021
unbatchedUpdates @ react-dom.development.js:22431
legacyRenderSubtreeIntoContainer @ react-dom.development.js:26020
render @ react-dom.development.js:26103
../node_modules/@redwoodjs/web/dist/entry/index.js @ index.js:24
options.factory @ react refresh:6
__webpack_require__ @ bootstrap:24
__webpack_exec__ @ valueFromASTUntyped.mjs:56
(anonymous) @ valueFromASTUntyped.mjs:56
webpackJsonpCallback @ jsonp chunk loading:560
(anonymous) @ app.bundle.js:1
App.js:12 

This is App.js

import { AuthProvider } from '@redwoodjs/auth'
import { createClient } from '@supabase/supabase-js'
import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import Routes from 'src/Routes'
import './scaffold.css'
import './index.css'

const supabaseClient = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_KEY
)

const App = () => (
  <FatalErrorBoundary page={FatalErrorPage}>
    <RedwoodProvider titleTemplate="%AppTitle | %PageTitle">
      <AuthProvider client={supabaseClient} type="supabase">
        <RedwoodApolloProvider>
          <Routes />
        </RedwoodApolloProvider>
      </AuthProvider>
    </RedwoodProvider>
  </FatalErrorBoundary>
)

export default App

rw info

  System:
    OS: macOS 11.6
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.9.1 - /var/folders/w7/hkfhzp4x68qcbr1ggchr7hx40000gn/T/yarn--1632667636561-0.45354533787100126/node
    Yarn: 1.22.11 - /var/folders/w7/hkfhzp4x68qcbr1ggchr7hx40000gn/T/yarn--1632667636561-0.45354533787100126/yarn
  Databases:
    SQLite: 3.32.3 - /usr/bin/sqlite3
  Browsers:
    Chrome: 93.0.4577.82
    Edge: 94.0.992.31
    Firefox: 92.0
    Safari: 14.1.2
  npmPackages:
    @redwoodjs/core: ^0.37.0-rc.60 => 0.37.0-rc.60+a551fe95 

✨  Done in 1.41s.

Any idea what could be wrong?

EDIT:

Simplified App.js to what’s in the template:

import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'

import FatalErrorPage from 'src/pages/FatalErrorPage'
import Routes from 'src/Routes'

import './index.css'

const App = () => (
  <FatalErrorBoundary page={FatalErrorPage}>
    <RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
      <RedwoodApolloProvider>
        <Routes />
      </RedwoodApolloProvider>
    </RedwoodProvider>
  </FatalErrorBoundary>
)

export default App

Still the same error

Dug in to node_modules to do some debugging

image

“creating ApolloProvder” is printed to my console, so it seems ApolloProvider is actually called

I solved it. Was an apollo client version mismatch. Had "@apollo/client": "^3.3.11" in web/package.json. Just deleted that line and now it works :slight_smile:

2 Likes

The upgrade process was super smooth, except for dbAuth. I probably forget a code change for /lib/auth.js last update, and I’m sure the doc for this PR coming, so I’m not worried.:wink: If someone else had troubled with dbAuth for this upgrade, you just need to compare api/src/lib/auth.{js,ts} with this template and api/src/functions/auth.ts with this template.

Also, I LOVE the new secure services with SDLs. Thank you so much @dthyresson and @danny for this amazing work. 🥲

1 Like

Thank you Simon for trying this out early - there’s a lot of changes this time, and its easy to miss things - its a great help!

I’ve added the dbAuth changes to the WIP release notes.

Also super excited about what people do with directives, and very happy you like them so much! We’re very pleased with the DX so far but always open to suggestions/PRs - the smiley cry emoji made me crack up!

1 Like

Also super excited about what people do with directives

I need to understand more about how directives works, but I’m sure is can be a good place for multitenancy logic, and it will be probably nice to have a setup CLI for directives when more use case will be defined.

the smiley cry emoji made me crack up!

I’m glad you like it, is really how I feel ahah

probably nice to have a setup CLI for directives

There already is :slight_smile: - just probably not finished documenting, but the cli should guide you through it

yarn rw g directive
1 Like
yarn rw g directive

Oh yes that’s good I like the boilerplate with transformer and validator type, but I was thinking something more like predefine template like requireAuth. It can also be a community base gists of directives to see what others devs do with it. I’m just really curious of what can be do with directives ! :grinning_face_with_smiling_eyes:

Update went really smooth for me! Only needed to do the required changes and everything started up without complaint.

Was even able to get it up and running in a serverless container, which was satisfying - all for science ofcourse, nobody depending on it in that state.

The one thing I will miss (and it’s a half-hearted missing, to be fair) that beforeResolvers brought with it, will be to be able to easily share input validation across service-endpoints. To my understanding, validators don’t have access to the data of a request, so it can’t be done there (which seems counterintuitive - think it’s just how I’m approaching the word “validation” - I know y’all are grouping authorization and authentication into the one term to make it) and then the transformers are only available for queries + run after them.

It was nice to say “hey I accept emails and any email should meet these requirements” and share that across endpoints without the endpoint needing to be aware of it - its responsibility was to just do something with that email. Now I’ve needed to refactor my app’s “validators” into calls made at the start of each service. An email is a trivial example, something more appropriate would be how my app provides multi-tenancy, so apart of fulfilling a request is asserting the invoking party has access to the resource they’re about to make a change against. Does this account ID the requesting user intends to update belong to their organization? When they give it a role is that check being made? Before deletion?

Asynchronous behavior works out-of-the-box, so that’s pleasant!

2 Likes

@dthyresson and @danny any thoughts on this? Seems like a custom directive + roles logic would do the trick.

So, Redwood directives are not intended to be fulfill the entire possible ways of using directives – but rather validating access before the execution phase (but after the context building phase – that’s when currentUser is loading into global context etc) and transforming a resolved value after execution is done but before the data is set in the returned resulting response.

While the validator is good for checking access, it does so with just the content info. Meaning “can I do this?” or “I I do this because I have this role?” or “can I do this because something in the event like some header value says I can?”. It’s not great for “Can I do this action on this bit of data because I am an owner”. That’s row level or data security that should be done at the query – in a where clause or using RLS.

For example, can I view “Team A” … I don’t want to get all teams and then filter in a directive the teams that I should see. I’d do that in a query. That is, I’d do it “during” execution, not before or after.

share input validation across service-endpoint

Also, validation is not meant to validate field data – for example, I am saving a form with an email and the email needs to be in a valid format.

It was nice to say “hey I accept emails and any email should meet these requirements” and share that across endpoints without the endpoint

We’d want to use graphql-scalars for that.

There’s a whole catalog of these like for email: graphql-scalars/src/scalars/EmailAddress.ts at master · Urigo/graphql-scalars · GitHub

See: Introduction – GraphQL Scalars and Introduction – GraphQL Scalars and graphql-scalars/src/scalars at master · Urigo/graphql-scalars · GitHub.

Custom scalars and also adding some of these common scalars is an enhancement to the RW GraphQL I’d like to add, too.

And you’d use it in your SDL like:

emailAddress Email!
phone PhoneNumber!
1 Like

Yes, converting over my role validator into a directive was a breeze, and it’s working just as expected! Even helped to consolidate some logic, is thank y’all for that.

As someone with no understanding of directives outside of Redwood, this is confusing. But it leads into the entirety of the confusion I’ve had with the whole Apollo to Envlope+Helix. I want desperately to understand it, but really don’t have the faintest idea how it’s all being tied together - or where my application’s customizations should go.

Again, it’s features that are literally coming out as we speak - I’d expect nothing less and, but it just adds to the confusion of being told “oh just make your own custom one”. one what? Or, ok I’ve got it now where does this thing go? :joy:

Please take this as a note for where documentation could be a big help, nothing more.

Like I said, sucks to loose the flexibility that the beforeResolver gave. I assume you’d want to classify non-database backed authorization within the execution phase of a service then. That kinda leads into my next few comments.

For reading, yes, it’s easy to filter results based on some column’s value - teamId - particularly when the data being read is stored by the app’s database. For writing, a where clause maybe a sufficient authorization (only saying I haven’t done it personally), but is this form of authorization (the decision maker being the database which is saying: I, team A’s manager, have access to all of team A’s members) not then restricted to only being usable within the context of one application or, more exact, through operations made directly against it’s database? Is the suggestion to have two sources of truth? One the redwood app uses on its own - it’s database, let’s say - and another which the redwood app could choose to use, but that is really just for the benefit of other, external services?

That’s all to say, where I could completely decouple authorization - any form of authorization (RLS, RBAC, …) - from the service before, now I need to couple it to the service’s invocation - it went from a pre-execution authorization to a during-execution-but-actually-its-pre-execution authorization now.

Just trying to adjust my mental model and make sure we’re picking up with the other is putting down.

Thank you for the suggestion, a Redwood offering would be very helpful as I’m sure quite a few are application-agnostic.

1 Like

The upgrade went really smooth for me. Was on 0.36-rc50 right before tho’.

5 Likes

@HoXsan
What auth provider are you using?

Did run into any problems related to your auth provider?

I am using dbAuth, and i did not have any problem :slight_smile:

2 Likes