Skip to content

Commit

Permalink
fix: improve error message upon wrong getContext() usage
Browse files Browse the repository at this point in the history
  • Loading branch information
brillout committed Oct 2, 2022
1 parent cf3f4f9 commit 0a0ac4e
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 39 deletions.
100 changes: 92 additions & 8 deletions docs/pages/getContext.page.server.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,99 @@
import { Link } from '@brillout/docpress'

Environment: Node.js.

Telefunctions often need context. Is the user logged-in? What is the user's ID? Etc.
The `getContext()` function enables you to provide contextual information to your telefunctions.

The most commmon use case is to provide information about the logged-in user:

```js
// TodoList.telefunc.js

export { fetchTodoItems }

import { getContext } from 'telefunc'

async function fetchTodoItems() {
const context = getContext()
const { user } = context
const todoItems = await Todo.findMany({ select: 'text', author: user.id })
return todoItems
}
```

You can define and provide the `context` object to Telefunc at your server middleware:

```js
// server.js
// Environment: Node.js

import { telefunc } from 'telefunc'

// Server middleware (Express.js/Fastify/Koa/Hapi/...)
app.all('/_telefunc', async (req, res) => {
// Authentication middlewares (e.g. Passport.js or Grant) usually provide information
// about the logged-in user on the `req` object.
const user = req.user

// Or when using a third-party authentication provider (e.g. Auth0):
const user = await authProviderApi.getUser(req.headers)

const context = { user }

const httpResponse = await telefunc({
// We define `context` here
context,
// The other `telefunc()` middleware arguments
url: req.url,
method: req.method,
body: req.body,
})
const { body, statusCode, contentType } = httpResponse
res.status(statusCode).type(contentType).send(body)
})
```

There are also other ways to pass the `context` object to Telefunc.


## Provide

How you pass the `context` object to Telefunc depends on your stack, see following integration guides.

- <Link href="/next" />
- <Link href="/nuxt" />
- <Link href="/vite" />
- <Link href="/install" />


## Not found

If you get this error:

```
[telefunc][getContext()] Context object not found, see https://telefunc.com/getContext#not-found
```

Then this usually means that you called `getContext()` after an `await` operator:

We use `getContext()` with `provideTelefuncContext()` in order to pass contextual information to telefunctions,
We use `getContext()` with `provideTelefuncContext()` in order to provide contextual information to our telefunctions.
such as the authenticated user's ID.
```js
// TodoList.telefunc.js

TODO
export async function myTelefunction() {
await someting()
// ❌ BAD: we should call `getContext()` before `await something()`
const context = getContext()
}
```

- Explain synchronous rule
- Explain synchronous nature of `getContext()`
Instead, make sure to call `getContext()` before any `await` operator:

```js
// TodoList.telefunc.js

> **Why?** If you wonder why ...
export async function myTelefunction() {
// ✅ GOOD: we call `getContext()` before any `await` operator
const context = getContext()
await someting()
}
```
1 change: 1 addition & 0 deletions docs/pages/install.page.server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Telefunc can be used in any JavaScript environment without using a transformer b

> Telefunc plugins transform `*.telefunc.js` browser-side imports into a thin HTTP client, see <Link href="/tour" />.

## Without transformer

Without TypeScript:
Expand Down
59 changes: 28 additions & 31 deletions telefunc/node/server/getContext.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,54 @@
import { getContext_sync, provideTelefuncContext_sync } from './getContext/sync'
import { assert, assertUsage, isObject } from '../utils'
import type { Telefunc } from './getContext/TelefuncNamespace'

export { getContext }
export { getContextOptional }
export { provideTelefuncContext }
export { Telefunc }

export { installAsyncMode }
installSyncMode()

import { getContext_sync, provideTelefuncContext_sync } from './getContext/sync'
import { assert, assertUsage, isObject, getGlobalObject } from '../utils'
import type { Telefunc } from './getContext/TelefuncNamespace'

type GetContext = () => Telefunc.Context | null
type ProvideTelefuncContext = (context: Telefunc.Context) => void

const globalObject = getGlobalObject<{
getContext: GetContext
provideTelefuncContext: ProvideTelefuncContext
neverProvided: boolean
}>('getContext.ts', {
getContext: getContext_sync,
provideTelefuncContext: provideTelefuncContext_sync,
neverProvided: true
})

function getContext<Context extends object = Telefunc.Context>(): Context {
const context = _getContext()
const context = globalObject.getContext()
assertUsage(
context !== null,
[
`\`getContext()\`: no context found${!isSSR() ? '' : ' (SSR)'},`,
'make sure to (properly) use `provideTelefuncContext()`,',
`see https://telefunc.com/provideTelefuncContext${isSSR() ? '#ssr' : ''}`
].join(' ')
globalObject.neverProvided === false,
'[getContext()] Make sure you provide a context object, see https://telefunc.com/getContext#provide'
)
assertUsage(context !== null, '[getContext()] No context object found, see https://telefunc.com/getContext#not-found')
assert(isObject(context))
return context as Context
}

function getContextOptional() {
const context = _getContext()
const context = globalObject.getContext()
return context
}

function provideTelefuncContext<Context extends object = Telefunc.Context>(context: Context) {
_provideTelefuncContext(context)
globalObject.neverProvided = false
globalObject.provideTelefuncContext(context)
}

var _getContext: () => Telefunc.Context | null
var _provideTelefuncContext: (context: Telefunc.Context) => void

function installSyncMode() {
_getContext = getContext_sync
_provideTelefuncContext = provideTelefuncContext_sync
}
async function installAsyncMode({
getContext_async,
provideTelefuncContext_async
}: {
getContext_async: typeof _getContext
provideTelefuncContext_async: typeof _provideTelefuncContext
getContext_async: GetContext
provideTelefuncContext_async: ProvideTelefuncContext
}) {
_getContext = getContext_async
_provideTelefuncContext = provideTelefuncContext_async
}

function isSSR(): boolean {
// TODO
return false
globalObject.getContext = getContext_async
globalObject.provideTelefuncContext = provideTelefuncContext_async
}

0 comments on commit 0a0ac4e

Please sign in to comment.