exisjs 0.7.11 → 0.7.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -39
- package/dist/cli/commands/dev.d.ts +4 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +136 -32
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/run.d.ts +7 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +99 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.js +29 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +18 -3
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/routes.d.ts +6 -0
- package/dist/cli/routes.d.ts.map +1 -1
- package/dist/cli/routes.js +224 -135
- package/dist/cli/routes.js.map +1 -1
- package/dist/config/config.d.ts +2 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +30 -9
- package/dist/config/types.d.ts.map +1 -1
- package/dist/database/lifecycle.d.ts +6 -1
- package/dist/database/lifecycle.d.ts.map +1 -1
- package/dist/database/lifecycle.js +15 -4
- package/dist/database/lifecycle.js.map +1 -1
- package/dist/decorators/params.d.ts +21 -0
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +21 -1
- package/dist/decorators/params.js.map +1 -1
- package/dist/di/container.d.ts +15 -6
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +26 -3
- package/dist/di/container.js.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/index.js.map +1 -1
- package/dist/di/inject.d.ts +2 -1
- package/dist/di/inject.d.ts.map +1 -1
- package/dist/di/inject.js +2 -1
- package/dist/di/inject.js.map +1 -1
- package/dist/docs/BENCHMARKS.md +4 -4
- package/dist/docs/content/README.md +43 -44
- package/dist/docs/content/boundaries.mdx +141 -48
- package/dist/docs/content/cli/commands.mdx +161 -107
- package/dist/docs/content/cli/overview.mdx +61 -46
- package/dist/docs/content/cli/usage.mdx +107 -30
- package/dist/docs/content/config.mdx +145 -147
- package/dist/docs/content/controllers.mdx +193 -464
- package/dist/docs/content/cron.mdx +183 -69
- package/dist/docs/content/decorators.mdx +449 -154
- package/dist/docs/content/errors.mdx +202 -305
- package/dist/docs/content/exisjs-mcp.mdx +59 -49
- package/dist/docs/content/installation.mdx +243 -157
- package/dist/docs/content/introduction.mdx +220 -48
- package/dist/docs/content/middleware.mdx +171 -497
- package/dist/docs/content/modules.mdx +141 -83
- package/dist/docs/content/providers.mdx +216 -180
- package/dist/docs/content/requests.mdx +168 -488
- package/dist/docs/content/responses.mdx +200 -533
- package/dist/docs/content/routing.mdx +239 -558
- package/dist/docs/content/security.mdx +233 -57
- package/dist/docs/content/structure.mdx +151 -240
- package/dist/docs/content/swagger.mdx +192 -91
- package/dist/docs/content/testing.mdx +205 -74
- package/dist/docs/content/validation.mdx +479 -341
- package/dist/docs/reference/README.md +34 -2
- package/dist/docs/reference/config.mdx +277 -73
- package/dist/docs/reference/context.mdx +197 -106
- package/dist/docs/reference/cron.mdx +266 -152
- package/dist/docs/reference/database.mdx +135 -53
- package/dist/docs/reference/decorators.mdx +234 -116
- package/dist/docs/reference/di.mdx +146 -112
- package/dist/docs/reference/error.mdx +155 -178
- package/dist/docs/reference/exis.mdx +141 -70
- package/dist/docs/reference/logger.mdx +110 -126
- package/dist/docs/reference/middleware.mdx +235 -158
- package/dist/docs/reference/plugin.mdx +136 -64
- package/dist/docs/reference/router.mdx +277 -251
- package/dist/docs/reference/sanitize.mdx +91 -87
- package/dist/docs/reference/swagger.mdx +280 -100
- package/dist/docs/reference/testing.mdx +223 -89
- package/dist/docs/reference/validator.mdx +194 -206
- package/dist/lib/inspect-routes.d.ts +2 -0
- package/dist/lib/inspect-routes.d.ts.map +1 -0
- package/dist/lib/inspect-routes.js +148 -0
- package/dist/lib/inspect-routes.js.map +1 -0
- package/dist/lib/start-server.js +13 -8
- package/dist/lib/start-server.js.map +1 -1
- package/dist/module/module.d.ts +6 -3
- package/dist/module/module.d.ts.map +1 -1
- package/dist/module/module.js +17 -3
- package/dist/module/module.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +26 -4
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/scanner/cron-mounter.d.ts.map +1 -1
- package/dist/router/scanner/cron-mounter.js +9 -2
- package/dist/router/scanner/cron-mounter.js.map +1 -1
- package/dist/server/app.d.ts +3 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +29 -8
- package/dist/server/app.js.map +1 -1
- package/dist/server/define.d.ts.map +1 -1
- package/dist/server/define.js +5 -0
- package/dist/server/define.js.map +1 -1
- package/dist/server/request.d.ts +2 -0
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +11 -0
- package/dist/server/request.js.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/swagger/generator.js +130 -10
- package/dist/swagger/generator.js.map +1 -1
- package/dist/telemetry/index.d.ts +12 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +121 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/testing/client.d.ts +1 -0
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +4 -1
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +20 -4
- package/dist/testing/index.js.map +1 -1
- package/dist/validator/builder.d.ts +26 -1
- package/dist/validator/builder.d.ts.map +1 -1
- package/dist/validator/builder.js +31 -0
- package/dist/validator/builder.js.map +1 -1
- package/dist/validator/engine.d.ts +23 -0
- package/dist/validator/engine.d.ts.map +1 -1
- package/dist/validator/engine.js +228 -31
- package/dist/validator/engine.js.map +1 -1
- package/dist/validator/index.d.ts +8 -4
- package/dist/validator/index.d.ts.map +1 -1
- package/dist/validator/index.js +4 -1
- package/dist/validator/index.js.map +1 -1
- package/dist/validator/tex-types.d.ts +15 -0
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/tex-types.js +102 -0
- package/dist/validator/tex-types.js.map +1 -1
- package/dist/validator/tex.d.ts +1 -1
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +3 -1
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +1 -0
- package/dist/validator/types.d.ts.map +1 -1
- package/package.json +47 -14
- package/dist/docs/content/dataloader.mdx +0 -170
- package/dist/docs/reference/dataloader.mdx +0 -91
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
nextUrl: "/docs/security"
|
|
3
|
-
nextTitle: "Security & Auth"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Dataloader (N+1 Queries)
|
|
7
|
-
|
|
8
|
-
The **N+1 problem** is a common performance issue in backend development. It occurs when your code executes one query to fetch a list of items (e.g. 100 Posts), and then executes $N$ additional queries to fetch related data (e.g. 100 separate queries for each Post's Author).
|
|
9
|
-
|
|
10
|
-
ExisJS ships with a native `Dataloader` implementation (inspired by GraphQL/Facebook's DataLoader) built directly into the core framework. It automatically **batches** and **caches** requests over a single tick of the Node.js event loop.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 1. Defining Loaders
|
|
15
|
-
|
|
16
|
-
The best practice in ExisJS is to define a central registry of Dataloaders that are **securely scoped to the current HTTP Request** (to prevent data leaking between different users).
|
|
17
|
-
|
|
18
|
-
ExisJS provides `defineLoaders` to create this context-aware registry.
|
|
19
|
-
|
|
20
|
-
```typescript filename="src/loaders.ts"
|
|
21
|
-
import { Dataloader, defineLoaders } from 'exisjs/dataloader'
|
|
22
|
-
import { getRequest } from 'exisjs/router'
|
|
23
|
-
import { db } from './db'
|
|
24
|
-
|
|
25
|
-
export const { loaderMiddleware, getLoaders } = defineLoaders({
|
|
26
|
-
// 1. Author Loader
|
|
27
|
-
authorLoader: () => new Dataloader<number, any>(async (keys) => {
|
|
28
|
-
// You can access the current HTTP Request anywhere using getRequest()
|
|
29
|
-
const req = getRequest()
|
|
30
|
-
const authToken = req.headers.authorization
|
|
31
|
-
|
|
32
|
-
// This function will only be called ONCE per event-loop tick,
|
|
33
|
-
// receiving an array of all requested Author IDs: e.g. [1, 2, 3]
|
|
34
|
-
const authors = await db.authors.findMany({
|
|
35
|
-
where: { id: { in: keys } }
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
// You MUST return an array of results in the EXACT same order as the keys
|
|
39
|
-
return keys.map(key => authors.find(a => a.id === key) || new Error('Not found'))
|
|
40
|
-
}),
|
|
41
|
-
|
|
42
|
-
// 2. Comments Loader
|
|
43
|
-
commentsLoader: () => new Dataloader<number, any[]>(async (keys) => {
|
|
44
|
-
const comments = await db.comments.findMany({
|
|
45
|
-
where: { postId: { in: keys } }
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// Group comments by postId
|
|
49
|
-
return keys.map(key => comments.filter(c => c.postId === key))
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## 2. Registering the Middleware
|
|
57
|
-
|
|
58
|
-
`defineLoaders` returns a `loaderMiddleware`. You must register this middleware so that ExisJS can securely inject a fresh, isolated instance of your Dataloaders into every incoming request.
|
|
59
|
-
|
|
60
|
-
Because ExisJS uses lazy-initialization, there is **zero performance penalty** for routes that don't actually use the loaders.
|
|
61
|
-
|
|
62
|
-
#### Functional Paradigm
|
|
63
|
-
|
|
64
|
-
```typescript filename="src/http/server.ts"
|
|
65
|
-
import { exis } from 'exisjs'
|
|
66
|
-
import { loaderMiddleware } from '../loaders'
|
|
67
|
-
|
|
68
|
-
export default exis({
|
|
69
|
-
async onStart(app) {
|
|
70
|
-
// Register globally for all routes
|
|
71
|
-
app.use(loaderMiddleware)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
#### Class-Based (OOP) Paradigm
|
|
77
|
-
|
|
78
|
-
```typescript filename="src/http/server.ts"
|
|
79
|
-
import { Server } from 'exisjs/decorators'
|
|
80
|
-
import { loaderMiddleware } from '../loaders'
|
|
81
|
-
import type { App } from 'exisjs'
|
|
82
|
-
|
|
83
|
-
@Server()
|
|
84
|
-
export default class RootServer {
|
|
85
|
-
async onStart(app: App) {
|
|
86
|
-
// Register globally for all routes
|
|
87
|
-
app.use(loaderMiddleware)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 3. Using Dataloader in Routes
|
|
95
|
-
|
|
96
|
-
Anywhere in your application (Routes, Controllers, Services), you can call `getLoaders()` to retrieve the securely isolated loaders for the *current* request.
|
|
97
|
-
|
|
98
|
-
#### Functional Paradigm
|
|
99
|
-
|
|
100
|
-
```typescript filename="src/http/posts/route.ts"
|
|
101
|
-
import { controller, route } from 'exisjs/router'
|
|
102
|
-
import { getLoaders } from '../../loaders'
|
|
103
|
-
|
|
104
|
-
export default controller({
|
|
105
|
-
list: route.get('/', {
|
|
106
|
-
async handle() {
|
|
107
|
-
// 1. Fetch 100 posts (1 Query)
|
|
108
|
-
const posts = await db.posts.findMany()
|
|
109
|
-
|
|
110
|
-
const { authorLoader } = getLoaders()
|
|
111
|
-
|
|
112
|
-
// 2. Loop through 100 posts and call .load()
|
|
113
|
-
const postsWithAuthors = await Promise.all(
|
|
114
|
-
posts.map(async (post) => {
|
|
115
|
-
// Instead of firing 100 queries to the database,
|
|
116
|
-
// Dataloader queues them up and fires exactly ONE batched query.
|
|
117
|
-
const author = await authorLoader.load(post.authorId)
|
|
118
|
-
return { ...post, author }
|
|
119
|
-
})
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
return postsWithAuthors
|
|
123
|
-
// Total Queries: 2 (Not 101)
|
|
124
|
-
}
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
#### Class-Based (OOP) Paradigm
|
|
130
|
-
|
|
131
|
-
```typescript filename="src/http/posts/route.ts"
|
|
132
|
-
import { Controller, Get } from 'exisjs/decorators'
|
|
133
|
-
import { getLoaders } from '../../loaders'
|
|
134
|
-
|
|
135
|
-
@Controller()
|
|
136
|
-
export default class PostsController {
|
|
137
|
-
@Get('/')
|
|
138
|
-
async list() {
|
|
139
|
-
// 1. Fetch 100 posts (1 Query)
|
|
140
|
-
const posts = await db.posts.findMany()
|
|
141
|
-
|
|
142
|
-
const { authorLoader } = getLoaders()
|
|
143
|
-
|
|
144
|
-
// 2. Loop through 100 posts and call .load()
|
|
145
|
-
const postsWithAuthors = await Promise.all(
|
|
146
|
-
posts.map(async (post) => {
|
|
147
|
-
// Instead of firing 100 queries to the database,
|
|
148
|
-
// Dataloader queues them up and fires exactly ONE batched query.
|
|
149
|
-
const author = await authorLoader.load(post.authorId)
|
|
150
|
-
return { ...post, author }
|
|
151
|
-
})
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
return postsWithAuthors
|
|
155
|
-
// Total Queries: 2 (Not 101)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## API Reference (`exisjs/dataloader`)
|
|
163
|
-
|
|
164
|
-
The `Dataloader` class exposes the standard, battle-tested API:
|
|
165
|
-
|
|
166
|
-
- **`load(key: K): Promise<V>`**: Queues a key to be fetched and returns a Promise.
|
|
167
|
-
- **`loadMany(keys: K[]): Promise<V[]>`**: Equivalent to calling `Promise.all(keys.map(k => load(k)))`.
|
|
168
|
-
- **`clear(key: K): this`**: Clears the cached result for a specific key.
|
|
169
|
-
- **`clearAll(): this`**: Clears the entire cache for this dataloader instance.
|
|
170
|
-
- **`prime(key: K, value: V): this`**: Primes the cache with a predefined value, preventing the batch function from being called for that key.
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Data Loader"
|
|
3
|
-
description: "Strict API Reference for the N+1 batching utility."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Data Loader (`exisjs/dataloader`)
|
|
7
|
-
|
|
8
|
-
This document provides the strict API signatures and TypeScript interfaces exported by the `exisjs/dataloader` module. For high-level guides on solving N+1 query problems, see the [Data Loader Guide](/docs/dataloader).
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## `Dataloader<K, V, C>` Class
|
|
13
|
-
|
|
14
|
-
The `Dataloader` class batches and caches requests to a backend data source.
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
import { Dataloader } from 'exisjs/dataloader'
|
|
18
|
-
|
|
19
|
-
class Dataloader<K, V, C = K> {
|
|
20
|
-
constructor(batchFn: BatchLoadFn<K, V>, options?: DataloaderOptions<K, C>)
|
|
21
|
-
|
|
22
|
-
// Enqueues a key to be batched, returning a Promise that resolves to the value
|
|
23
|
-
load(key: K): Promise<V>
|
|
24
|
-
|
|
25
|
-
// Loads multiple keys, returning an array of Promises
|
|
26
|
-
loadMany(keys: readonly K[]): Promise<V[]>
|
|
27
|
-
|
|
28
|
-
// Clears the value at `key` from the cache
|
|
29
|
-
clear(key: K): this
|
|
30
|
-
|
|
31
|
-
// Clears all values from the cache
|
|
32
|
-
clearAll(): this
|
|
33
|
-
|
|
34
|
-
// Primes the cache with a predefined value or Error for a specific key
|
|
35
|
-
prime(key: K, value: V | Error): this
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### `BatchLoadFn<K, V>`
|
|
40
|
-
|
|
41
|
-
The function responsible for loading the batched keys. **It must return an array of values (or Errors) that are precisely the same length and in the exact same order as the provided keys array.**
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
type BatchLoadFn<K, V> = (
|
|
45
|
-
keys: readonly K[]
|
|
46
|
-
) => Promise<readonly (V | Error)[]>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### `DataloaderOptions<K, C>`
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
interface DataloaderOptions<K, C = K> {
|
|
53
|
-
// Whether to cache resolved keys within this Dataloader instance. Default: true
|
|
54
|
-
cache?: boolean
|
|
55
|
-
|
|
56
|
-
// The maximum number of keys to include in a single batch. Default: 1000
|
|
57
|
-
maxBatchSize?: number
|
|
58
|
-
|
|
59
|
-
// A function to generate a unique cache key from the provided load key
|
|
60
|
-
cacheKeyFn?: (key: K) => C
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## Context Registry (`defineLoaders`)
|
|
67
|
-
|
|
68
|
-
Because `Dataloader` instances are inherently stateful (they cache data), they **must not** be shared across different HTTP requests, or else users might read each other's cached data.
|
|
69
|
-
|
|
70
|
-
The `defineLoaders` function securely isolates Dataloader instances per-request by binding them to the ExisJS asynchronous context.
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
import { defineLoaders } from 'exisjs/dataloader'
|
|
74
|
-
|
|
75
|
-
function defineLoaders<
|
|
76
|
-
T extends Record<string, () => Dataloader<any, any>>
|
|
77
|
-
>(factories: T): {
|
|
78
|
-
// Middleware to register onto your router/app
|
|
79
|
-
loaderMiddleware: (req, res, next) => void;
|
|
80
|
-
|
|
81
|
-
// Hook to retrieve the isolated loaders anywhere in the request lifecycle
|
|
82
|
-
getLoaders: () => { [K in keyof T]: ReturnType<T[K]> }
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Usage Signature
|
|
87
|
-
```typescript
|
|
88
|
-
const { loaderMiddleware, getLoaders } = defineLoaders({
|
|
89
|
-
userLoader: () => new Dataloader(async (keys) => { /* ... */ })
|
|
90
|
-
})
|
|
91
|
-
```
|