-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: improve error message upon wrong
getContext()
usage
- Loading branch information
Showing
3 changed files
with
121 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |