@mantajs/core 0.2.0-beta.1 → 0.2.0-beta.10
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/dist/adapters/file-memory.d.ts +2 -5
- package/dist/adapters/file-memory.d.ts.map +1 -1
- package/dist/adapters/file-memory.js +1 -1
- package/dist/adapters/file-memory.js.map +1 -1
- package/dist/config/types.d.ts +36 -16
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +4 -2
- package/dist/config/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ports/file.d.ts +9 -5
- package/dist/ports/file.d.ts.map +1 -1
- package/dist/ports/index.d.ts +1 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/query/define-query.d.ts +17 -1
- package/dist/query/define-query.d.ts.map +1 -1
- package/dist/query/define-query.js.map +1 -1
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +7 -1
- package/dist/query/index.js.map +1 -1
- package/dist/service/define.d.ts +9 -1
- package/dist/service/define.d.ts.map +1 -1
- package/dist/service/define.js.map +1 -1
- package/dist/service/instantiate.d.ts +7 -1
- package/dist/service/instantiate.d.ts.map +1 -1
- package/dist/service/instantiate.js +9 -2
- package/dist/service/instantiate.js.map +1 -1
- package/dist/user/auto-routes.d.ts +6 -0
- package/dist/user/auto-routes.d.ts.map +1 -1
- package/dist/user/auto-routes.js +70 -8
- package/dist/user/auto-routes.js.map +1 -1
- package/docs/00-overview.md +9 -10
- package/docs/01-getting-started.md +26 -29
- package/docs/03-services.md +23 -5
- package/docs/04-users.md +47 -3
- package/docs/05-commands.md +1 -1
- package/docs/06-queries.md +54 -23
- package/docs/10-spa.md +63 -1
- package/docs/11-config.md +158 -39
- package/docs/14-adapters.md +52 -2
- package/docs/15-hosts.md +17 -2
- package/docs/16-reference.md +32 -3
- package/docs/17-dashboard.md +4 -2
- package/docs/AGENT.md +51 -4
- package/package.json +5 -3
- package/skills/mantajs/SKILL.md +67 -0
- package/skills/mantajs/agents/openai.yaml +3 -0
package/docs/06-queries.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
# Queries — defineQuery()
|
|
1
|
+
# Queries — Drizzle-first defineQuery()
|
|
2
2
|
|
|
3
|
-
Queries are the CQRS read side. They expose data as GET endpoints.
|
|
3
|
+
Queries are the CQRS read side. They expose data as GET endpoints.
|
|
4
4
|
|
|
5
|
-
- `defineQuery()`
|
|
6
|
-
- `defineQueryGraph()`
|
|
5
|
+
- `defineQuery()` is the primary application API. New code should query with the schema-aware Drizzle client.
|
|
6
|
+
- `defineQueryGraph()` is reserved for generated/admin/dynamic surfaces that need entity-level browsing.
|
|
7
|
+
|
|
8
|
+
Manta does not try to replace SQL. `defineModel()` generates the Drizzle schema and migrations; `defineQuery()` gives handlers the Drizzle client, generated schema, tagged SQL helper, auth context, logging, and request headers.
|
|
7
9
|
|
|
8
10
|
## defineQuery()
|
|
9
11
|
|
|
10
12
|
```typescript
|
|
11
13
|
// src/queries/admin/list-products.ts
|
|
14
|
+
import { eq } from 'drizzle-orm'
|
|
12
15
|
import { z } from 'zod'
|
|
13
16
|
|
|
14
17
|
export default defineQuery({
|
|
@@ -18,12 +21,19 @@ export default defineQuery({
|
|
|
18
21
|
status: z.string().optional(),
|
|
19
22
|
...listParams(),
|
|
20
23
|
}),
|
|
21
|
-
handler: async (input, {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
handler: async (input, { drizzle, schema, sql }) => {
|
|
25
|
+
const products = schema.products
|
|
26
|
+
return drizzle
|
|
27
|
+
.select({
|
|
28
|
+
id: products.id,
|
|
29
|
+
title: products.title,
|
|
30
|
+
status: products.status,
|
|
31
|
+
totalVariants: sql<number>`count(*) over()::int`,
|
|
32
|
+
})
|
|
33
|
+
.from(products)
|
|
34
|
+
.where(input.status ? eq(products.status, input.status) : undefined)
|
|
35
|
+
.limit(input.limit)
|
|
36
|
+
.offset(input.offset)
|
|
27
37
|
},
|
|
28
38
|
})
|
|
29
39
|
```
|
|
@@ -43,14 +53,21 @@ defineQuery({
|
|
|
43
53
|
|
|
44
54
|
```typescript
|
|
45
55
|
{
|
|
46
|
-
|
|
56
|
+
drizzle: unknown, // schema-aware Drizzle client
|
|
57
|
+
db: unknown, // alias for drizzle
|
|
58
|
+
schema: Record<string, unknown>, // generated Drizzle tables
|
|
59
|
+
sql: typeof import('drizzle-orm').sql, // tagged SQL helper
|
|
60
|
+
database: IDatabasePort, // raw SQL, pool, transactions
|
|
61
|
+
query: QueryService, // generated/admin query graph
|
|
47
62
|
log: ILoggerPort, // Structured logging
|
|
48
63
|
auth: AuthContext | null, // Authenticated user
|
|
49
64
|
headers: Record<string, string | undefined>, // Raw request headers
|
|
50
65
|
}
|
|
51
66
|
```
|
|
52
67
|
|
|
53
|
-
|
|
68
|
+
Prefer Drizzle for all application reads, including simple lists, dashboards, analytics, counts, search, and joins. Use `sql` fragments when a PostgreSQL feature is clearer than query-builder syntax.
|
|
69
|
+
|
|
70
|
+
`query.graph()` remains available for generated admin screens and dynamic entity browsing. It must not be used to hide analytical work in JavaScript. If a query needs `COUNT`, `SUM`, `GROUP BY`, `FILTER`, CTEs, window functions, full-text search, or dashboard KPIs, write it as Drizzle/SQL in `defineQuery()`.
|
|
54
71
|
|
|
55
72
|
### Routing
|
|
56
73
|
|
|
@@ -65,6 +82,18 @@ Query parameters are passed as URL query string:
|
|
|
65
82
|
GET /api/admin/list-products?status=active&limit=10&offset=0
|
|
66
83
|
```
|
|
67
84
|
|
|
85
|
+
### Serverless fast routes
|
|
86
|
+
|
|
87
|
+
For serverless and worker builds, Manta generates route-level functions for compatible `defineQuery()` files. These fast routes do not import `bootstrapApp()`, jobs, workflows, AI, command registries, or the catch-all Manta adapter.
|
|
88
|
+
|
|
89
|
+
Fast routes are generated when:
|
|
90
|
+
|
|
91
|
+
- the project uses filesystem V2 contexts (`src/queries/{context}`), not legacy `src/contexts/*.ts`
|
|
92
|
+
- the file exports `defineQuery()`, not `defineQueryGraph()`
|
|
93
|
+
- the query does not call `query.graph()`, `query.graphAndCount()`, `query.index()`, or `query.gql()`
|
|
94
|
+
|
|
95
|
+
Pure synthetic queries do not initialize the database or generated schema. Queries that reference `drizzle`, `db`, `schema`, or `database` initialize only the DB adapter and generated Drizzle schema needed by the handler.
|
|
96
|
+
|
|
68
97
|
### Input helpers
|
|
69
98
|
|
|
70
99
|
```typescript
|
|
@@ -85,22 +114,24 @@ z.object({
|
|
|
85
114
|
### Auth in queries
|
|
86
115
|
|
|
87
116
|
```typescript
|
|
88
|
-
handler: async (input, {
|
|
117
|
+
handler: async (input, { drizzle, schema, auth }) => {
|
|
89
118
|
// auth.id — user ID
|
|
90
119
|
// auth.type — context ('admin', 'customer')
|
|
91
120
|
// auth.email — user email
|
|
92
121
|
|
|
93
122
|
// Example: only return MY orders
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
123
|
+
return drizzle
|
|
124
|
+
.select()
|
|
125
|
+
.from(schema.orders)
|
|
126
|
+
.where(eq(schema.orders.customer_id, auth!.id))
|
|
98
127
|
}
|
|
99
128
|
```
|
|
100
129
|
|
|
101
130
|
## defineQueryGraph()
|
|
102
131
|
|
|
103
|
-
Exposes the query graph to the frontend. The frontend can compose
|
|
132
|
+
Exposes the query graph to the frontend. The frontend can compose entity browsing queries for generated/admin screens.
|
|
133
|
+
|
|
134
|
+
Do not use the graph as the main application query language. It is intentionally less expressive than SQL. Complex reads belong in `defineQuery()` with Drizzle.
|
|
104
135
|
|
|
105
136
|
### Wildcard — full access (admin/AI)
|
|
106
137
|
|
|
@@ -163,11 +194,11 @@ POST /api/admin/graph
|
|
|
163
194
|
|
|
164
195
|
| Use case | Primitive | Why |
|
|
165
196
|
|----------|-----------|-----|
|
|
166
|
-
|
|
|
167
|
-
| Storefront
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
| API
|
|
197
|
+
| Generated admin entity table | `defineQueryGraph('*')` | Dynamic browsing over known entities |
|
|
198
|
+
| Storefront catalog | `defineQuery` | Stable contract, optimized Drizzle query |
|
|
199
|
+
| Customer orders | `defineQuery` | Auth-scoped SQL with explicit filters |
|
|
200
|
+
| Dashboard / analytics | `defineQuery` | Aggregations run in Postgres |
|
|
201
|
+
| Third-party API | `defineQuery` | Fixed contract, no graph exposure |
|
|
171
202
|
|
|
172
203
|
## extendQueryGraph() — external entity resolvers
|
|
173
204
|
|
package/docs/10-spa.md
CHANGED
|
@@ -34,7 +34,13 @@ src/spa/admin/
|
|
|
34
34
|
- **`.ts`** — exports `definePage()` or `defineForm()` spec (declarative, no React)
|
|
35
35
|
- **`.tsx`** — exports a React component (full control, for complex cases)
|
|
36
36
|
|
|
37
|
-
**Defaults**: `@mantajs/dashboard` shell + `@mantajs/ui` preset.
|
|
37
|
+
**Defaults**: `@mantajs/dashboard` shell + `@mantajs/ui` preset. The generated entry imports the public stylesheet export:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import '@mantajs/dashboard/index.css'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Override in config if needed:
|
|
38
44
|
|
|
39
45
|
```typescript
|
|
40
46
|
// manta.config.ts — optional, only for overrides
|
|
@@ -46,6 +52,62 @@ export default defineConfig({
|
|
|
46
52
|
})
|
|
47
53
|
```
|
|
48
54
|
|
|
55
|
+
### Mount path
|
|
56
|
+
|
|
57
|
+
By default, a SPA named `admin` is mounted at `/admin`:
|
|
58
|
+
|
|
59
|
+
| Generated setting | Default for `src/spa/admin` |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| React Router basename | `/admin` |
|
|
62
|
+
| Vite `base` | `/admin/` |
|
|
63
|
+
| Static output | `public/admin` |
|
|
64
|
+
| Vercel SPA rewrite | `/admin/:path*` → `/admin/index.html` |
|
|
65
|
+
|
|
66
|
+
Use `mountPath: '/'` for a dashboard on a dedicated subdomain, such as `admin.fancypalas.com/paniers`:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// manta.config.ts
|
|
70
|
+
export default defineConfig({
|
|
71
|
+
spa: {
|
|
72
|
+
admin: { mountPath: '/' },
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
With `mountPath: '/'`, React Router uses `basename="/"`, Vite uses `base: '/'`, the SPA builds into `public/`, and generated Vercel rewrites exclude `/api/*`.
|
|
78
|
+
|
|
79
|
+
### Dev Vite port
|
|
80
|
+
|
|
81
|
+
`manta dev` starts one Vite server per detected SPA. Ports default to `5200`, then increment for additional SPAs.
|
|
82
|
+
|
|
83
|
+
Configure the port in `manta.config.ts` when another project already uses the default:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
export default defineConfig({
|
|
87
|
+
spa: {
|
|
88
|
+
admin: { vitePort: 5310 },
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Environment overrides are also supported:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
MANTA_VITE_PORT=5310 manta dev
|
|
97
|
+
MANTA_VITE_PORT_ADMIN=5311 manta dev
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The SPA-specific variable wins over the global one. The suffix uses the SPA name uppercased with non-alphanumeric characters replaced by `_`.
|
|
101
|
+
|
|
102
|
+
### Local production fallback
|
|
103
|
+
|
|
104
|
+
`manta build` writes a SPA fallback manifest into `.manta/server/spa-manifest.ts`. For the Node preset, `manta start` uses it to mirror the Vercel SPA behavior locally:
|
|
105
|
+
|
|
106
|
+
- `/` redirects to the single SPA mount path when the SPA is not mounted at `/`
|
|
107
|
+
- `/login`, `/reset-password`, and `/accept-invite` redirect under that mount path
|
|
108
|
+
- `/admin/*` and other configured SPA mount paths serve the built `index.html`
|
|
109
|
+
- `/api/*` and asset URLs remain server-side/static and are not swallowed by the SPA fallback
|
|
110
|
+
|
|
49
111
|
---
|
|
50
112
|
|
|
51
113
|
## SPA Configuration — defineSpa()
|
package/docs/11-config.md
CHANGED
|
@@ -17,12 +17,15 @@ Creates a complete, functional Manta project. After `pnpm install`, the project
|
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
my-app/
|
|
20
|
-
├── manta.config.ts # defineConfig() — database, http,
|
|
20
|
+
├── manta.config.ts # defineConfig() — database, http, spa, presets
|
|
21
|
+
├── nitro.config.ts # Nitro host config for standalone builds
|
|
21
22
|
├── package.json # @mantajs/core, @mantajs/cli, @mantajs/host-nitro, @mantajs/dashboard
|
|
22
23
|
├── tsconfig.json # ES2022, ESNext, JSX support, strict
|
|
23
24
|
├── .env / .env.example # DATABASE_URL, PORT, ANTHROPIC_API_KEY
|
|
24
|
-
├── .gitignore # .manta/,
|
|
25
|
+
├── .gitignore # .manta/, node_modules/, .env
|
|
25
26
|
├── AGENT.md # AI instructions (copied from @mantajs/core/docs/)
|
|
27
|
+
├── .codex/skills/mantajs/ # Codex skill pointing to the shipped Manta docs
|
|
28
|
+
├── .claude/skills/mantajs/ # Claude skill pointing to the same docs
|
|
26
29
|
└── src/
|
|
27
30
|
├── modules/ # Your business modules (entities + services)
|
|
28
31
|
├── commands/ # Application commands (cross-module workflows)
|
|
@@ -31,48 +34,44 @@ my-app/
|
|
|
31
34
|
├── links/ # Cross-module relations
|
|
32
35
|
├── queries/ # CQRS read endpoints (defineQuery, defineQueryGraph)
|
|
33
36
|
├── agents/ # AI steps
|
|
34
|
-
└── admin/
|
|
35
|
-
├──
|
|
36
|
-
|
|
37
|
+
└── spa/admin/ # Dashboard SPA pages, config, blocks
|
|
38
|
+
├── config.ts
|
|
39
|
+
├── pages/
|
|
40
|
+
└── blocks/
|
|
37
41
|
```
|
|
38
42
|
|
|
39
43
|
**What it does NOT generate:**
|
|
40
|
-
- No `nitro.config.ts` — the host adapter handles this internally
|
|
41
44
|
- No `drizzle.config.ts` — the CLI handles this internally
|
|
42
45
|
- No `src/api/` — routes are auto-generated from commands + queries
|
|
43
46
|
|
|
44
|
-
The
|
|
47
|
+
The developer normally maintains `manta.config.ts`, `nitro.config.ts` only when deployment/static asset behavior needs customization, and the generated `AGENT.md` when project-specific conventions need to be added.
|
|
45
48
|
|
|
46
|
-
###
|
|
49
|
+
### Existing projects
|
|
50
|
+
|
|
51
|
+
The current CLI does not expose a separate `manta setup` command. Existing projects should install or update the Manta packages, keep a `manta.config.ts` at the project root, then run:
|
|
47
52
|
|
|
48
53
|
```bash
|
|
49
|
-
|
|
50
|
-
npx manta setup
|
|
54
|
+
manta dev
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
| Detected | Context | What it generates |
|
|
56
|
-
|----------|---------|------------------|
|
|
57
|
-
| `next.config.*` | Next.js | `AGENT.md` with Next.js + Manta instructions, `src/manta/` subdirectory |
|
|
58
|
-
| `nuxt.config.*` | Nuxt | `AGENT.md` with Nuxt + Manta instructions, `server/manta/` subdirectory |
|
|
59
|
-
| `workspaces` in package.json | Monorepo | `AGENT.md` at root, `packages/backend/` with Manta structure |
|
|
60
|
-
| Nothing detected | Standalone | Full Manta project structure in current directory |
|
|
57
|
+
`manta dev` refreshes **`.codex/skills/mantajs/SKILL.md`** and **`.claude/skills/mantajs/SKILL.md`** from the installed `@mantajs/core` package before boot validation. This keeps Codex and Claude pointed at the docs shipped in `node_modules/@mantajs/core/docs/` even when a project already existed before the current framework version.
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
1. Creates `manta.config.ts`
|
|
64
|
-
2. Creates module/command/subscriber directories
|
|
65
|
-
3. Generates **`AGENT.md` at the project root** — the first file an AI reads
|
|
66
|
-
4. The `AGENT.md` is context-aware (mentions the detected framework)
|
|
67
|
-
5. `AGENT.md` is committed to git (not gitignored) — every clone has it
|
|
68
|
-
|
|
69
|
-
### The AGENT.md
|
|
59
|
+
### Agent docs and Codex skill
|
|
70
60
|
|
|
71
61
|
The `AGENT.md` lives at the root of every Manta project. It's the first file an AI reads.
|
|
72
62
|
|
|
73
63
|
**Canonical location:** `@mantajs/core/docs/AGENT.md` — alongside all other framework documentation. The CLI copies it to the project root during `manta init`.
|
|
74
64
|
|
|
75
|
-
|
|
65
|
+
The Codex skill is intentionally separate and smaller:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
.codex/skills/mantajs/SKILL.md
|
|
69
|
+
.claude/skills/mantajs/SKILL.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It is copied from `@mantajs/core/skills/mantajs/SKILL.md` and tells Codex/Claude to load the relevant file from `node_modules/@mantajs/core/docs/` before changing Manta code. It is not installed with an npm `postinstall` hook; `manta init` creates it and `manta dev` refreshes it so existing projects stay aligned with the installed framework docs.
|
|
73
|
+
|
|
74
|
+
For future setup flows and framework-specific projects, context-specific templates exist in the CLI:
|
|
76
75
|
|
|
77
76
|
```
|
|
78
77
|
packages/cli/src/templates/agent/
|
|
@@ -80,7 +79,7 @@ packages/cli/src/templates/agent/
|
|
|
80
79
|
└── nuxt.md # Nuxt + Manta
|
|
81
80
|
```
|
|
82
81
|
|
|
83
|
-
`manta init` copies the canonical AGENT.md from `@mantajs/core/docs/`.
|
|
82
|
+
`manta init` copies the canonical AGENT.md from `@mantajs/core/docs/`. Framework-specific templates can be used by future setup flows or custom scaffolding. The developer can then edit the project AGENT.md to add project-specific context (business domain, team conventions, etc.).
|
|
84
83
|
|
|
85
84
|
Each AGENT.md contains:
|
|
86
85
|
- Stack description (what framework + Manta)
|
|
@@ -100,7 +99,9 @@ export default {
|
|
|
100
99
|
pool: { min: 2, max: 10 },
|
|
101
100
|
},
|
|
102
101
|
http: { port: 3000 },
|
|
103
|
-
|
|
102
|
+
spa: {
|
|
103
|
+
admin: {}, // mounted at /admin by default
|
|
104
|
+
},
|
|
104
105
|
}
|
|
105
106
|
```
|
|
106
107
|
|
|
@@ -113,7 +114,12 @@ export default defineConfig({
|
|
|
113
114
|
auth: {
|
|
114
115
|
jwtSecret: process.env.JWT_SECRET,
|
|
115
116
|
},
|
|
116
|
-
|
|
117
|
+
spa: {
|
|
118
|
+
admin: {
|
|
119
|
+
// Default mount path is "/admin"; use "/" for admin.example.com routes.
|
|
120
|
+
mountPath: '/admin',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
117
123
|
strict: false,
|
|
118
124
|
})
|
|
119
125
|
```
|
|
@@ -127,12 +133,61 @@ export default defineConfig({
|
|
|
127
133
|
| `database` | `url`, `pool.min`, `pool.max` | Required |
|
|
128
134
|
| `http` | `port` | `9000` |
|
|
129
135
|
| `auth` | `jwtSecret`, `session.enabled`, `session.cookieName` | Dev: auto-generated secret |
|
|
130
|
-
| `query` | `
|
|
131
|
-
| `admin` |
|
|
132
|
-
| `
|
|
136
|
+
| `query` | `maxTotalEntities` | `10000` |
|
|
137
|
+
| `admin` | Reserved legacy object | `{}` |
|
|
138
|
+
| `spa` | `{ [name]: { dashboard, preset, mountPath, vitePort } }` | Auto-detected from `src/spa/{name}` |
|
|
139
|
+
| `preset` | `'dev'`, `'vercel'`, custom preset object | Auto-detected from `APP_ENV` |
|
|
133
140
|
| `strict` | `true`/`false` | `false` |
|
|
134
141
|
| `plugins` | Array of plugin configs | `[]` |
|
|
135
142
|
|
|
143
|
+
### SPA mount path
|
|
144
|
+
|
|
145
|
+
Standalone/Nitro SPAs are discovered from `src/spa/{name}/`. By default, the public mount path is `/{name}`:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
export default defineConfig({
|
|
149
|
+
spa: {
|
|
150
|
+
admin: {}, // mounted at /admin
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For a dedicated admin subdomain such as `admin.fancypalas.com`, mount the SPA at the root:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
export default defineConfig({
|
|
159
|
+
spa: {
|
|
160
|
+
admin: { mountPath: '/' },
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
With `mountPath: '/'`, the Vite SPA uses `base: '/'`, builds into `public/`, and generated Vercel rewrites exclude `/api/*` so API routes remain server-side.
|
|
166
|
+
|
|
167
|
+
### SPA dev port
|
|
168
|
+
|
|
169
|
+
`manta dev` starts SPA Vite servers at `5200`, then increments for additional SPAs. Use `vitePort` when a local project already owns that port:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
export default defineConfig({
|
|
173
|
+
spa: {
|
|
174
|
+
admin: {
|
|
175
|
+
mountPath: '/admin',
|
|
176
|
+
vitePort: 5310,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Environment overrides are supported for temporary local conflicts:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
MANTA_VITE_PORT=5310 manta dev
|
|
186
|
+
MANTA_VITE_PORT_ADMIN=5311 manta dev
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Precedence is `spa.{name}.vitePort`, then `MANTA_VITE_PORT_{NAME}`, then `MANTA_VITE_PORT`, then the default sequence.
|
|
190
|
+
|
|
136
191
|
## Presets (adapter bundles)
|
|
137
192
|
|
|
138
193
|
The framework auto-detects the environment and loads appropriate adapters:
|
|
@@ -149,6 +204,64 @@ The framework auto-detects the environment and loads appropriate adapters:
|
|
|
149
204
|
|
|
150
205
|
Detection: `APP_ENV` > `NODE_ENV` > default `'development'`.
|
|
151
206
|
|
|
207
|
+
### Vercel Blob access
|
|
208
|
+
|
|
209
|
+
The Vercel preset wires `IFilePort` to `@mantajs/adapter-file-vercel-blob`. The adapter defaults to `access: 'public'` for compatibility with existing stores. For sensitive files, create a private Vercel Blob store and configure the adapter explicitly:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
export default defineConfig({
|
|
213
|
+
preset: 'vercel',
|
|
214
|
+
adapters: {
|
|
215
|
+
IFilePort: {
|
|
216
|
+
adapter: '@mantajs/adapter-file-vercel-blob',
|
|
217
|
+
options: {
|
|
218
|
+
access: 'private',
|
|
219
|
+
signedUrlExpiresInSeconds: 300,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Private file reads use Vercel Blob's server-side `get()` API via `getDownloadStream()`/`getAsBuffer()`. If the application needs a browser URL, `getPresignedDownloadUrl()` returns a short-lived signed URL instead of exposing the private Blob URL directly.
|
|
227
|
+
|
|
228
|
+
### Durable cache for auth revocation
|
|
229
|
+
|
|
230
|
+
Production auth uses `ICachePort` for logout token revocation, refresh-token blacklist checks, reset flows, and session state. The Vercel and Cloudflare presets default to Upstash:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
export default defineConfig({
|
|
234
|
+
preset: 'vercel',
|
|
235
|
+
adapters: {
|
|
236
|
+
ICachePort: {
|
|
237
|
+
adapter: '@mantajs/adapter-cache-upstash',
|
|
238
|
+
options: {
|
|
239
|
+
url: process.env.UPSTASH_REDIS_REST_URL,
|
|
240
|
+
token: process.env.UPSTASH_REDIS_REST_TOKEN,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
If Upstash REST credentials are not available or not reliable in the deployment runtime, use the Postgres-backed cache adapter:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
export default defineConfig({
|
|
251
|
+
preset: 'vercel',
|
|
252
|
+
adapters: {
|
|
253
|
+
ICachePort: {
|
|
254
|
+
adapter: '@mantajs/adapter-database-pg/cache',
|
|
255
|
+
options: {
|
|
256
|
+
tableName: 'manta_cache',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The Postgres cache reuses the configured `IDatabasePort` connection when bootstrapped by the Manta CLI and creates the cache table automatically by default.
|
|
264
|
+
|
|
152
265
|
## Environment variables
|
|
153
266
|
|
|
154
267
|
| Variable | Required | Description |
|
|
@@ -156,6 +269,8 @@ Detection: `APP_ENV` > `NODE_ENV` > default `'development'`.
|
|
|
156
269
|
| `DATABASE_URL` | Yes | PostgreSQL connection string |
|
|
157
270
|
| `JWT_SECRET` | Prod only | JWT signing secret (dev: auto-generated) |
|
|
158
271
|
| `APP_ENV` | No | Force environment (`development` or `production`) |
|
|
272
|
+
| `UPSTASH_REDIS_REST_URL` | Prod when using Upstash cache | Upstash Redis REST endpoint |
|
|
273
|
+
| `UPSTASH_REDIS_REST_TOKEN` | Prod when using Upstash cache | Upstash Redis REST token |
|
|
159
274
|
| `ANTHROPIC_API_KEY` | No | Enable AI chat in admin dashboard |
|
|
160
275
|
|
|
161
276
|
## CLI commands
|
|
@@ -178,15 +293,21 @@ Detection: `APP_ENV` > `NODE_ENV` > default `'development'`.
|
|
|
178
293
|
|
|
179
294
|
Starts development server. Auto-performs:
|
|
180
295
|
1. Load env + config
|
|
181
|
-
2. Generate `.manta/
|
|
296
|
+
2. Generate `.manta/generated.d.ts` (TypeScript types for autocomplete)
|
|
182
297
|
3. Start Nitro dev server with HMR
|
|
183
298
|
4. On first request: connect DB, instantiate adapters, discover modules/commands/subscribers/jobs/links/queries
|
|
184
299
|
5. Auto-create tables in dev mode (no manual migration needed)
|
|
185
300
|
6. Wire all routes, subscribers, jobs
|
|
186
301
|
|
|
302
|
+
### manta build --preset vercel
|
|
303
|
+
|
|
304
|
+
For Vercel deployments, `manta build --preset vercel` generates the framework-owned SPA rewrites and cron routes in `vercel.json`.
|
|
305
|
+
|
|
306
|
+
If `vercel.json` already exists, Manta preserves custom top-level project settings such as `buildCommand`, `installCommand`, `outputDirectory`, `functions`, `headers`, and other Vercel options. It also preserves custom `redirects`, `rewrites`, and `crons` unless they collide on the same `source` or `path` as a framework-generated entry.
|
|
307
|
+
|
|
187
308
|
### manta db:generate
|
|
188
309
|
|
|
189
|
-
Scans `src/modules/*/
|
|
310
|
+
Scans entity definitions from `src/modules/*/entities/` and relation links from `src/links/` to generate SQL migrations:
|
|
190
311
|
|
|
191
312
|
```bash
|
|
192
313
|
manta db:generate --name add-blog-post
|
|
@@ -212,15 +333,13 @@ manta exec scripts/seed.ts
|
|
|
212
333
|
manta exec scripts/seed.ts --dry-run # Rollback after execution (test mode)
|
|
213
334
|
```
|
|
214
335
|
|
|
215
|
-
## Codegen (
|
|
336
|
+
## Codegen (`.manta/generated.d.ts`)
|
|
216
337
|
|
|
217
338
|
On `manta dev` or `manta build`, the framework generates:
|
|
218
339
|
|
|
219
340
|
| File | Content |
|
|
220
341
|
|------|---------|
|
|
221
|
-
|
|
|
222
|
-
| `app.d.ts` | `MantaAppModules` — typed `app.modules.catalog.listProducts()` |
|
|
223
|
-
| `events.d.ts` | `MantaEventName` — union of all known event names |
|
|
342
|
+
| `.manta/generated.d.ts` | `MantaEntities`, `MantaAppModules`, `MantaEventName`, and module augmentation used by IDE autocomplete |
|
|
224
343
|
|
|
225
344
|
These are TypeScript module augmentations. After codegen, `app.modules.*` has full autocomplete in your IDE.
|
|
226
345
|
|
package/docs/14-adapters.md
CHANGED
|
@@ -8,7 +8,7 @@ Every infrastructure concern is abstracted behind a port. Here are all ports you
|
|
|
8
8
|
|
|
9
9
|
| Port | Responsibility | Dev adapter | Prod adapter |
|
|
10
10
|
|------|---------------|-------------|--------------|
|
|
11
|
-
| `ICachePort` | Key-value cache | InMemoryCacheAdapter | UpstashCacheAdapter |
|
|
11
|
+
| `ICachePort` | Key-value cache | InMemoryCacheAdapter | UpstashCacheAdapter, PostgresCacheAdapter |
|
|
12
12
|
| `IEventBusPort` | Event pub/sub | InMemoryEventBusAdapter | Upstash Queues |
|
|
13
13
|
| `IFilePort` | File storage | InMemoryFileAdapter | VercelBlobAdapter |
|
|
14
14
|
| `ILoggerPort` | Structured logging | TestLogger | PinoLoggerAdapter |
|
|
@@ -67,6 +67,25 @@ export class RedisCacheAdapter implements ICachePort {
|
|
|
67
67
|
}
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
## Built-in durable Postgres cache
|
|
71
|
+
|
|
72
|
+
When Redis/Upstash is not desirable, `@mantajs/adapter-database-pg` exposes a durable cache adapter:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
export default defineConfig({
|
|
76
|
+
adapters: {
|
|
77
|
+
ICachePort: {
|
|
78
|
+
adapter: '@mantajs/adapter-database-pg/cache',
|
|
79
|
+
options: {
|
|
80
|
+
tableName: 'manta_cache',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The CLI factory reuses the active `IDatabasePort` Postgres connection. This adapter is appropriate for auth logout/reset/session revocation state where durability matters more than sub-millisecond Redis latency.
|
|
88
|
+
|
|
70
89
|
### 2. Create the barrel export
|
|
71
90
|
|
|
72
91
|
```typescript
|
|
@@ -131,8 +150,17 @@ interface IEventBusPort {
|
|
|
131
150
|
### IFilePort
|
|
132
151
|
|
|
133
152
|
```typescript
|
|
153
|
+
type FileAccess = 'public' | 'private'
|
|
154
|
+
|
|
155
|
+
interface FileUploadResult {
|
|
156
|
+
key: string
|
|
157
|
+
url: string
|
|
158
|
+
access?: FileAccess
|
|
159
|
+
downloadUrl?: string
|
|
160
|
+
}
|
|
161
|
+
|
|
134
162
|
interface IFilePort {
|
|
135
|
-
upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<
|
|
163
|
+
upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<FileUploadResult>
|
|
136
164
|
delete(key: string | string[]): Promise<void>
|
|
137
165
|
getPresignedDownloadUrl(key: string): Promise<string>
|
|
138
166
|
getDownloadStream(key: string): Promise<ReadableStream>
|
|
@@ -141,6 +169,28 @@ interface IFilePort {
|
|
|
141
169
|
}
|
|
142
170
|
```
|
|
143
171
|
|
|
172
|
+
#### Vercel Blob access mode
|
|
173
|
+
|
|
174
|
+
`@mantajs/adapter-file-vercel-blob` supports both Vercel Blob access modes:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
export default defineConfig({
|
|
178
|
+
adapters: {
|
|
179
|
+
IFilePort: {
|
|
180
|
+
adapter: '@mantajs/adapter-file-vercel-blob',
|
|
181
|
+
options: {
|
|
182
|
+
access: 'private',
|
|
183
|
+
signedUrlExpiresInSeconds: 300,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`access: 'public'` is the default for backward compatibility and should be reserved for public assets. For sensitive admin files, snapshots, invoices, or user content, use a Vercel Blob store created as private and set `access: 'private'`.
|
|
191
|
+
|
|
192
|
+
Private blobs are read through the server SDK in `getDownloadStream()` and `getAsBuffer()`, so the read-write token is never sent to the browser. `getPresignedDownloadUrl()` returns a short-lived signed URL for private blobs. You can also set `MANTA_VERCEL_BLOB_ACCESS=private` or `MANTA_FILE_ACCESS=private` when configuring through environment variables.
|
|
193
|
+
|
|
144
194
|
### ILockingPort
|
|
145
195
|
|
|
146
196
|
```typescript
|
package/docs/15-hosts.md
CHANGED
|
@@ -17,6 +17,8 @@ Manta ships with a Nitro host (`packages/host-nitro/`). It:
|
|
|
17
17
|
- Handles the catch-all route `server/routes/[...].ts`
|
|
18
18
|
- Passes requests through to Manta's 12-step pipeline
|
|
19
19
|
|
|
20
|
+
For serverless and worker production builds, Nitro also receives generated route-level `defineQuery()` handlers under `server/routes/api/{context}/{query}.ts` when the query is compatible with the fast runtime. Those handlers bypass the catch-all route and do not call `bootstrapApp()`. The catch-all remains the fallback for command graphs, query graphs, workflows, custom API routes, auth routes, cron routes, and legacy context setups.
|
|
21
|
+
|
|
20
22
|
## Creating a custom host
|
|
21
23
|
|
|
22
24
|
### Example: Express host
|
|
@@ -33,7 +35,7 @@ export async function startExpressHost(options: { port?: number; cwd?: string }
|
|
|
33
35
|
// Bootstrap the Manta app (same as manta dev / manta start)
|
|
34
36
|
const { app, adapter, logger, shutdown } = await bootstrapApp({
|
|
35
37
|
cwd,
|
|
36
|
-
mode: '
|
|
38
|
+
mode: 'prod',
|
|
37
39
|
})
|
|
38
40
|
|
|
39
41
|
// Create Express app
|
|
@@ -84,7 +86,7 @@ let bootstrapped: Awaited<ReturnType<typeof bootstrapApp>> | null = null
|
|
|
84
86
|
export async function handler(event: APIGatewayProxyEventV2) {
|
|
85
87
|
// Cold start: bootstrap once
|
|
86
88
|
if (!bootstrapped) {
|
|
87
|
-
bootstrapped = await bootstrapApp({ cwd: process.cwd(), mode: '
|
|
89
|
+
bootstrapped = await bootstrapApp({ cwd: process.cwd(), mode: 'prod' })
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// Translate Lambda event to Web Request
|
|
@@ -117,6 +119,19 @@ A host must:
|
|
|
117
119
|
|
|
118
120
|
The H3Adapter's `handleRequest(request: Request): Promise<Response>` is the single entry point. It runs the full 12-step pipeline (auth, validation, routing, error handling).
|
|
119
121
|
|
|
122
|
+
## Production bootstrap mode
|
|
123
|
+
|
|
124
|
+
Production hosts must call `bootstrapApp()` with `mode: 'prod'`. Development tooling uses `mode: 'dev'`.
|
|
125
|
+
|
|
126
|
+
The Nitro production build generated by `@mantajs/host-nitro` writes a production bootstrap with `mode: 'prod'` for both manifest-based builds and runtime-discovery fallback builds. This matters because production error handling must not expose development stack traces.
|
|
127
|
+
|
|
128
|
+
For the Node preset, the generated Nitro catch-all route calls the Manta API first, then serves SPA fallbacks from the built static output when the API returns `404` for a `GET` request. This mirrors the generated Vercel routing locally:
|
|
129
|
+
|
|
130
|
+
- `/` redirects to the single SPA mount path when there is one SPA mounted away from `/`
|
|
131
|
+
- `/login`, `/reset-password`, and `/accept-invite` redirect under that mount path
|
|
132
|
+
- SPA subroutes such as `/admin/login` return the built `index.html`
|
|
133
|
+
- `/api/*` and file-extension asset paths are never handled by the SPA fallback
|
|
134
|
+
|
|
120
135
|
## What the pipeline provides
|
|
121
136
|
|
|
122
137
|
Every request passing through `adapter.handleRequest()` goes through:
|