@cloudgrid-io/mcp 0.11.0 → 0.11.2
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/package.json +2 -1
- package/src/corpus/capability-map.md +26 -23
- package/src/corpus/cloudgrid-yaml.md +569 -0
- package/src/corpus/examples/app-with-data/index.md +24 -19
- package/src/corpus/templates/app-with-data/README.md +22 -19
- package/src/corpus/templates/app-with-data/cloudgrid.yaml +9 -13
- package/src/corpus/templates/app-with-data/index.md +37 -38
- package/src/corpus/templates/app-with-data/services/web/lib/db.js +10 -9
- package/src/corpus/troubleshooting/persistent-apps.md +6 -6
- package/src/corpus/workflows/app-with-data.md +21 -18
- package/src/tools.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "MCP server for CloudGrid. Three editions: a local stdio server (full toolset), a hosted web server (light, CLI-free toolset), and a public docs server (search_cloudgrid_documentation) over MCP Streamable HTTP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"test:source": "node test/source-fetch.test.mjs",
|
|
22
22
|
"test:app-with-data": "node test/app-with-data.test.mjs",
|
|
23
23
|
"test:self-describing": "node test/self-describing.test.mjs",
|
|
24
|
+
"test:canonical-yaml": "node test/canonical-yaml.test.mjs",
|
|
24
25
|
"test:content": "node test/content-handling.test.mjs",
|
|
25
26
|
"test:self-healing": "node test/self-healing.test.mjs",
|
|
26
27
|
"test:grid-picker": "node test/grid-picker.test.mjs",
|
|
@@ -8,7 +8,10 @@ template by its `when:` triggers, adopt that template's `needs:`, then deploy:
|
|
|
8
8
|
- **anything needing `needs:` → runtime** (async build, **local edition** only)
|
|
9
9
|
via `gridctl_plug` on a linked folder.
|
|
10
10
|
|
|
11
|
-
Fetch this doc any time with `gridctl_fetch("doc", "capability-map")`.
|
|
11
|
+
Fetch this doc any time with `gridctl_fetch("doc", "capability-map")`. For the full
|
|
12
|
+
cloudgrid.yaml schema (every field, the `needs:` injection table, the
|
|
13
|
+
requires-vs-needs caveat, validation rules), fetch the companion reference:
|
|
14
|
+
`gridctl_fetch("doc", "cloudgrid-yaml")`.
|
|
12
15
|
|
|
13
16
|
## The 6 templates
|
|
14
17
|
|
|
@@ -29,35 +32,35 @@ users/sessions, log in, or store submissions → it is persistent → `app-with-
|
|
|
29
32
|
|
|
30
33
|
`needs:` is a MAP declaring infrastructure capabilities. Values are `true` or an
|
|
31
34
|
engine hint. Cron is NOT a need — it is a **service type** (`type: cron` with
|
|
32
|
-
`schedule` + `timezone`). See the cloudgrid.yaml reference §5
|
|
35
|
+
`schedule` + `timezone`). See the full cloudgrid.yaml reference §5–§6 via
|
|
36
|
+
`gridctl_fetch("doc", "cloudgrid-yaml")`.
|
|
33
37
|
|
|
34
38
|
| `needs:` key | Provides | Injected env var(s) | Status |
|
|
35
39
|
|---|---|---|---|
|
|
36
|
-
| `database: true` | MongoDB primary datastore | `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) | **
|
|
37
|
-
| `cache: true` | Redis, LRU eviction | `CACHE_REDIS_URL` (+legacy `REDIS_URL`) | **
|
|
38
|
-
| `kv: true` | Redis, no eviction | `KV_REDIS_URL` |
|
|
39
|
-
| `queue: true` | Redis durable job queue | `QUEUE_REDIS_URL` |
|
|
40
|
-
| `pubsub: true` | Redis pub/sub broadcast | `PUBSUB_REDIS_URL` |
|
|
41
|
-
| `vector: pgvector` | pgvector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) |
|
|
42
|
-
| `object_storage: true` | GCS bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` |
|
|
43
|
-
| `disk: true` | Persistent filesystem at `/data` | `DISK_PATH` |
|
|
44
|
-
| `ai: true` | AI Gateway access | `AI_GATEWAY_URL` |
|
|
40
|
+
| `database: true` | MongoDB primary datastore | `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) | **Injects via `needs:`** |
|
|
41
|
+
| `cache: true` | Redis, LRU eviction | `CACHE_REDIS_URL` (+legacy `REDIS_URL`) | **Injects via `needs:`** |
|
|
42
|
+
| `kv: true` | Redis, no eviction | `KV_REDIS_URL` | Injects via `needs:` |
|
|
43
|
+
| `queue: true` | Redis durable job queue | `QUEUE_REDIS_URL` | Injects via `needs:` |
|
|
44
|
+
| `pubsub: true` | Redis pub/sub broadcast | `PUBSUB_REDIS_URL` | Injects via `needs:` |
|
|
45
|
+
| `vector: pgvector` | pgvector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | **Injects via `needs:`** |
|
|
46
|
+
| `object_storage: true` | GCS bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | Injects via `needs:` |
|
|
47
|
+
| `disk: true` | Persistent filesystem at `/data` | `DISK_PATH` | Injects via `needs:` |
|
|
48
|
+
| `ai: true` | AI Gateway access | `AI_GATEWAY_URL` | Injects via `needs:` |
|
|
45
49
|
| `type: cron` (service) | Scheduled job (`schedule`, `timezone`) | — | Service type, not a need |
|
|
46
50
|
|
|
47
|
-
### Injection status
|
|
51
|
+
### Injection status
|
|
48
52
|
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
- **`needs:` injects the connection env vars today.** `needs: { database: true }`
|
|
54
|
+
→ `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`); `needs: { cache: true }` →
|
|
55
|
+
`CACHE_REDIS_URL` (+legacy `REDIS_URL`); `needs: { vector: pgvector }` →
|
|
56
|
+
`VECTOR_PGVECTOR_URL`; and so on across the nine needs. Author `needs:`.
|
|
57
|
+
(*Historical note: the deployer once did not inject from `needs:` — platform
|
|
58
|
+
bug #1527, now fixed and verified live.*)
|
|
59
|
+
- **`requires:` is the deprecated v1 alias.** Don't author new yaml with it.
|
|
56
60
|
- **`needs:` and `requires:` cannot both be active** in one yaml — the validator
|
|
57
|
-
rejects the combination ("use one or the other"). So a DB template ships
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
metadata), flipping to `needs:` when #1527 lands.
|
|
61
|
+
rejects the combination ("use one or the other"). So a DB template ships the
|
|
62
|
+
canonical `needs: { database: true }` active, with NO `requires:` (and declares
|
|
63
|
+
`needs: database` in the workflow frontmatter as metadata).
|
|
61
64
|
|
|
62
65
|
## How to choose
|
|
63
66
|
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
# cloudgrid.yaml reference — the full manifest schema for agents
|
|
2
|
+
|
|
3
|
+
> **Canonical source:** `cloudgrid-yaml-reference.md` in the CloudGrid platform
|
|
4
|
+
> repo. This is the agent-facing distribution
|
|
5
|
+
> of that reference — practically complete, not a verbatim dump. **Keep in sync**
|
|
6
|
+
> with the upstream: when the platform reference changes, update this doc.
|
|
7
|
+
>
|
|
8
|
+
> Fetch this doc any time with `gridctl_fetch("doc", "cloudgrid-yaml")`.
|
|
9
|
+
> For matching an intent to a template, see the companion
|
|
10
|
+
> `gridctl_fetch("doc", "capability-map")`.
|
|
11
|
+
|
|
12
|
+
`cloudgrid.yaml` is the manifest at the **root of your project** that defines your
|
|
13
|
+
entity (an app or an agent). It declares the services your entity runs, the
|
|
14
|
+
infrastructure it needs, and how it behaves. `grid init` creates it; `grid plug`
|
|
15
|
+
reads it to build and deploy.
|
|
16
|
+
|
|
17
|
+
**Wiring a database or cache? Use `needs:`** — `needs: {database: true}` is
|
|
18
|
+
canonical and the deployer injects the connection env vars from it. See §7 for the
|
|
19
|
+
`needs:`-vs-`requires:` note. `requires:` is the deprecated v1 alias; don't author
|
|
20
|
+
new yaml with it.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Minimal, real, deployable examples
|
|
25
|
+
|
|
26
|
+
These are the shapes verified against live deploys. Copy one and adapt it.
|
|
27
|
+
|
|
28
|
+
### Static site → deploys as an inspiration (instant)
|
|
29
|
+
|
|
30
|
+
A single-file / static bundle. No `needs:`. Publishes instantly via
|
|
31
|
+
`gridctl_drop`, works on any edition (hosted or local).
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
name: landing-page
|
|
35
|
+
services:
|
|
36
|
+
web:
|
|
37
|
+
type: static
|
|
38
|
+
path: /
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Node.js app
|
|
42
|
+
|
|
43
|
+
Code lives under `services/web/` (`src/index.js` + `package.json`). Runtime deploy
|
|
44
|
+
(async, local edition only).
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
name: my-api
|
|
48
|
+
services:
|
|
49
|
+
web:
|
|
50
|
+
type: node
|
|
51
|
+
path: /
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Next.js app
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
name: my-dashboard
|
|
58
|
+
services:
|
|
59
|
+
web:
|
|
60
|
+
type: nextjs
|
|
61
|
+
path: /
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Next.js + database — the canonical shape
|
|
65
|
+
|
|
66
|
+
Declare the datastore with `needs: {database: true}`. The deployer provisions
|
|
67
|
+
Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias) at
|
|
68
|
+
dev-time and runtime. `requires:` is the deprecated v1 alias — don't author new
|
|
69
|
+
yaml with it, and never set `needs:` and `requires:` together (the validator
|
|
70
|
+
rejects the combination). See §7 for the details.
|
|
71
|
+
|
|
72
|
+
This mirrors the live-verified `app-with-data` template exactly: app code under
|
|
73
|
+
`services/web/`, and the app reads `process.env.DATABASE_MONGODB_URL` (falling
|
|
74
|
+
back to the legacy `MONGODB_URL`) **lazily** (inside a getter, never at module top
|
|
75
|
+
level — a top-level read fails `next build`).
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
# Rename this app. The grid injects the DB connection string (and a Redis URL if
|
|
79
|
+
# you add a cache) as environment variables at runtime — do NOT set them
|
|
80
|
+
# yourself, and never commit a connection string or secret.
|
|
81
|
+
#
|
|
82
|
+
# `needs: { database: true }` is canonical: the deployer provisions Mongo and
|
|
83
|
+
# injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL alias). `requires:` is
|
|
84
|
+
# the deprecated v1 alias — don't mix the two (the validator rejects it).
|
|
85
|
+
name: my-app
|
|
86
|
+
services:
|
|
87
|
+
web:
|
|
88
|
+
type: nextjs
|
|
89
|
+
path: /
|
|
90
|
+
needs:
|
|
91
|
+
database: true
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Agent — a cron job
|
|
95
|
+
|
|
96
|
+
An agent is an app with `kind: agent` and an `agent:` block. Cron is a **service
|
|
97
|
+
type**, not a need. `type: cron` requires a `schedule` and cannot have `path` or
|
|
98
|
+
`port`.
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
name: nightly-sync
|
|
102
|
+
kind: agent
|
|
103
|
+
agent:
|
|
104
|
+
purpose: Sync data from an external API every night
|
|
105
|
+
services:
|
|
106
|
+
sync:
|
|
107
|
+
type: cron
|
|
108
|
+
schedule: "0 9 * * *" # 9am daily
|
|
109
|
+
timezone: UTC
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> A cron/agent that also needs a database declares it the canonical way:
|
|
113
|
+
> `needs: {database: true}` (not the deprecated `requires:`).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 2. Full annotated example (the kitchen sink)
|
|
118
|
+
|
|
119
|
+
Every supported field, commented. **No real project uses all of these at once** —
|
|
120
|
+
this is a reference, not a template. (The `needs:` block here is the canonical
|
|
121
|
+
schema — the deployer injects the connection env vars from it; see §7.)
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
# ─── Identity ───────────────────────────────────────────────────────
|
|
125
|
+
name: my-app # Entity slug (required). 2-42 chars, lowercase + hyphens.
|
|
126
|
+
description: Order processing API # One-line summary (max 280 chars). Optional.
|
|
127
|
+
icon: "📦" # Single emoji or short text (max 64 chars). Optional.
|
|
128
|
+
kind: app # 'app' (default) or 'agent'. Optional.
|
|
129
|
+
|
|
130
|
+
# ─── Networking ─────────────────────────────────────────────────────
|
|
131
|
+
expose: true # Serve public HTTP traffic (default: true).
|
|
132
|
+
# Set to false for internal-only entities.
|
|
133
|
+
custom_domains: # Additional FQDNs the entity serves on. Optional.
|
|
134
|
+
- orders.example.com
|
|
135
|
+
|
|
136
|
+
# ─── Health probe (entity-level override) ───────────────────────────
|
|
137
|
+
probe: # HTTP health probe override. Optional.
|
|
138
|
+
path: /healthz # Probe path (default: /).
|
|
139
|
+
expected: "<500" # Expected status: '<500' (default), '200', '2xx'.
|
|
140
|
+
|
|
141
|
+
# ─── Deploy notifications ──────────────────────────────────────────
|
|
142
|
+
notify: true # Slack notification on deploy failure (default: true).
|
|
143
|
+
|
|
144
|
+
# ─── Scaling ────────────────────────────────────────────────────────
|
|
145
|
+
scale: auto # 'auto' (default), 'alert', or integer 1-10.
|
|
146
|
+
|
|
147
|
+
# ─── Infrastructure needs (v2, CANONICAL — the deployer injects from these) ─
|
|
148
|
+
needs:
|
|
149
|
+
database: true # Mongo (org-tier). Injects DATABASE_MONGODB_URL.
|
|
150
|
+
cache: true # Redis with LRU eviction. Injects CACHE_REDIS_URL.
|
|
151
|
+
kv: true # Redis, no eviction. Injects KV_REDIS_URL.
|
|
152
|
+
queue: true # Redis for BullMQ etc. Injects QUEUE_REDIS_URL.
|
|
153
|
+
pubsub: true # Redis pub/sub. Injects PUBSUB_REDIS_URL.
|
|
154
|
+
vector: pgvector # pgvector. Injects VECTOR_PGVECTOR_URL.
|
|
155
|
+
object_storage: true # GCS bucket. Injects OBJECT_STORAGE_GCS_BUCKET.
|
|
156
|
+
disk: true # Persistent filesystem at /data. Injects DISK_PATH.
|
|
157
|
+
ai: true # AI Gateway access. Injects AI_GATEWAY_URL.
|
|
158
|
+
|
|
159
|
+
# ─── Inter-entity communication ────────────────────────────────────
|
|
160
|
+
calls: # Entities this app calls (outbound grants).
|
|
161
|
+
- org:atomic/billing-api
|
|
162
|
+
- org:atomic/content-service
|
|
163
|
+
|
|
164
|
+
callers: # Inbound caller policy.
|
|
165
|
+
policy: auto # 'auto' or 'manual' (default: 'manual').
|
|
166
|
+
scope: org # 'org', 'space', or 'explicit' (default: 'org').
|
|
167
|
+
fallback: deny # 'deny' or 'warn' (default: 'deny').
|
|
168
|
+
|
|
169
|
+
# ─── Vault ──────────────────────────────────────────────────────────
|
|
170
|
+
vault: # Map of ENV_VAR: vault_item_key. Optional.
|
|
171
|
+
STRIPE_KEY: stripe-live-key
|
|
172
|
+
SENDGRID_API_KEY: sendgrid-key
|
|
173
|
+
|
|
174
|
+
# ─── Connectors and hooks ──────────────────────────────────────────
|
|
175
|
+
connectors:
|
|
176
|
+
- id: team-slack
|
|
177
|
+
target: org # 'system' or 'org'
|
|
178
|
+
channel: "#deploys"
|
|
179
|
+
|
|
180
|
+
hooks: # Outbound events. on ∈ release.published |
|
|
181
|
+
- on: deploy.failed # deploy.failed | feedback.received | entity.created
|
|
182
|
+
via: team-slack # Must match a connector id above.
|
|
183
|
+
template: "Deploy failed for {entity_name}: {error}"
|
|
184
|
+
|
|
185
|
+
triggers: # Inbound events routed into your app.
|
|
186
|
+
- from: team-slack
|
|
187
|
+
event: message
|
|
188
|
+
channel: "#support"
|
|
189
|
+
route: /webhooks/slack # Path in your app that receives the POST.
|
|
190
|
+
|
|
191
|
+
# ─── Local development ─────────────────────────────────────────────
|
|
192
|
+
dev:
|
|
193
|
+
snapshot:
|
|
194
|
+
mask: # Fields to redact in dev snapshots (collection.field).
|
|
195
|
+
- users.email
|
|
196
|
+
- users.phone
|
|
197
|
+
|
|
198
|
+
# ─── Services ──────────────────────────────────────────────────────
|
|
199
|
+
services:
|
|
200
|
+
web:
|
|
201
|
+
type: nextjs # 'node', 'nextjs', 'python', 'static', or 'cron'.
|
|
202
|
+
path: / # HTTP route prefix. false = internal-only.
|
|
203
|
+
port: 3000 # Container port (default: 8080).
|
|
204
|
+
base_image: distroless # 'alpine' or 'distroless' (nextjs only).
|
|
205
|
+
health: # Health probe config.
|
|
206
|
+
path: /api/health
|
|
207
|
+
expected: "200"
|
|
208
|
+
env: # Static env vars (not secrets).
|
|
209
|
+
LOG_LEVEL: info
|
|
210
|
+
depends_on: # Wait for these before starting.
|
|
211
|
+
- worker
|
|
212
|
+
|
|
213
|
+
worker:
|
|
214
|
+
type: node
|
|
215
|
+
path: false # No public URL — internal service.
|
|
216
|
+
source:
|
|
217
|
+
path: src/worker # Custom source location (instead of services/worker/).
|
|
218
|
+
needs: # Per-service needs override (merged per-key).
|
|
219
|
+
queue: true # Adds queue (inherits database, cache from app-level).
|
|
220
|
+
persist: # Service-level persistence.
|
|
221
|
+
- name: scratch
|
|
222
|
+
path: /tmp/scratch
|
|
223
|
+
size: 500Mi
|
|
224
|
+
|
|
225
|
+
reports:
|
|
226
|
+
type: cron
|
|
227
|
+
schedule: "0 9 * * 1" # Every Monday at 9am.
|
|
228
|
+
timezone: UTC # 'UTC', 'EST', or 'PST'.
|
|
229
|
+
run: job # 'job' (run code) or an HTTP URL.
|
|
230
|
+
|
|
231
|
+
frontend:
|
|
232
|
+
type: static
|
|
233
|
+
path: /app
|
|
234
|
+
build: # Build step for static sites.
|
|
235
|
+
command: npm run build
|
|
236
|
+
output: dist
|
|
237
|
+
env:
|
|
238
|
+
VITE_API_URL: https://api.example.com
|
|
239
|
+
node_version: "22" # Node version for build: '18', '20', '22', '24'. STRING, not int.
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 3. Top-level fields
|
|
245
|
+
|
|
246
|
+
### Required
|
|
247
|
+
|
|
248
|
+
| Field | Type | Description |
|
|
249
|
+
|-------|------|-------------|
|
|
250
|
+
| `name` | string | Entity slug. 2-42 chars, lowercase alphanumeric + hyphens, starts/ends with a letter or digit. Becomes part of the public URL. |
|
|
251
|
+
| `services` | map | One or more named service configs. See §4. |
|
|
252
|
+
|
|
253
|
+
### Optional
|
|
254
|
+
|
|
255
|
+
| Field | Type | Default | Description |
|
|
256
|
+
|-------|------|---------|-------------|
|
|
257
|
+
| `description` | string | — | One-line summary, max 280 chars. Auto-generated by Grid Brain if unset. |
|
|
258
|
+
| `icon` | string | — | Single emoji or short text, max 64 chars. |
|
|
259
|
+
| `kind` | string | `app` | `app` or `agent` (an agent is an app with an `agent:` block). |
|
|
260
|
+
| `expose` | boolean | `true` | Whether the entity serves public HTTP. `false` = internal-only. |
|
|
261
|
+
| `custom_domains` | string[] | — | Additional FQDNs served. |
|
|
262
|
+
| `probe` | object | — | Entity-level health probe override: `{ path, expected }`. |
|
|
263
|
+
| `notify` | boolean | `true` | Slack notification on deploy failure. |
|
|
264
|
+
| `scale` | string \| int | `auto` | `auto`, `alert`, or integer 1-10. |
|
|
265
|
+
| `needs` | object | — | Infrastructure capabilities (canonical). See §5 + caveat box. |
|
|
266
|
+
| `calls` | string[] | — | Entities this app calls outbound. |
|
|
267
|
+
| `callers` | object | — | Inbound caller policy. |
|
|
268
|
+
| `vault` | map | — | `ENV_VAR: vault_item_key` mappings. |
|
|
269
|
+
| `connectors` / `hooks` / `triggers` | array | — | Connector refs, outbound event hooks, inbound trigger webhooks. |
|
|
270
|
+
| `dev` | object | — | Local development config (`grid dev`). |
|
|
271
|
+
| `agent` | object | — | Agent metadata (loosely typed: `purpose`, `schedule`, `trigger`). |
|
|
272
|
+
| `auto_depends_on_private_requires` | boolean | `true` | Auto-inject private resources as service deps. |
|
|
273
|
+
|
|
274
|
+
### Deprecated (still accepted)
|
|
275
|
+
|
|
276
|
+
| Field | Replacement | Notes |
|
|
277
|
+
|-------|-------------|-------|
|
|
278
|
+
| `domain` | `custom_domains` | Single custom domain string. |
|
|
279
|
+
| `network` | `expose` | `{ public: bool }`. |
|
|
280
|
+
| `requires` | `needs` | v1 infra requirements. Deprecated — author `needs:` instead. **Cannot be mixed with `needs`** (the validator rejects the combination). |
|
|
281
|
+
| `persist` (top-level) | `needs.disk` | v1 entity-level filesystem persistence. |
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 4. Services
|
|
286
|
+
|
|
287
|
+
Each service is a named entry under `services:`. The name is the service identifier
|
|
288
|
+
(same slug rules as the entity name). **By default, a service's code lives at
|
|
289
|
+
`services/<service-name>/`** — `path:` is the URL mount, NOT the filesystem path. A
|
|
290
|
+
service named `web` → the CLI looks for `services/web/`. (Override with `source.path`.)
|
|
291
|
+
|
|
292
|
+
### Service types
|
|
293
|
+
|
|
294
|
+
| Type | What it runs | Required files | Default port | `path`? |
|
|
295
|
+
|------|-------------|----------------|--------------|---------|
|
|
296
|
+
| `node` | Node.js server | `src/index.js` (or `.ts`), `package.json` | 8080 | yes |
|
|
297
|
+
| `nextjs` | Next.js app (standalone output) | `next.config.*`, `package.json` | 8080 | yes |
|
|
298
|
+
| `python` | Python server | `src/main.py`, `requirements.txt` | 8080 | yes |
|
|
299
|
+
| `static` | Static files served by nginx (optional `build`) | `index.html` or build output | 8080 | yes |
|
|
300
|
+
| `cron` | Scheduled job | `src/job.js` or `src/main.py` | none | **no** — needs `schedule`, no `path`/`port` |
|
|
301
|
+
|
|
302
|
+
### Service fields
|
|
303
|
+
|
|
304
|
+
| Field | Type | Required | Default | Applies to |
|
|
305
|
+
|-------|------|----------|---------|-----------|
|
|
306
|
+
| `type` | string | Yes | — | All |
|
|
307
|
+
| `path` | string \| `false` | No | — | All except cron |
|
|
308
|
+
| `port` | integer (1-65535) | No | 8080 | All except cron |
|
|
309
|
+
| `lang` | string | No | — | node only (`javascript` \| `typescript`) |
|
|
310
|
+
| `base_image` | string | No | `distroless` | nextjs only (`alpine` \| `distroless`) |
|
|
311
|
+
| `source` | object | No | — | All — `{ path }`, default `services/<name>/`, `.` = project root |
|
|
312
|
+
| `build` | object | No | — | static only — `{ command, output, env }` |
|
|
313
|
+
| `node_version` | string | No | `"22"` | static build (`"18"`/`"20"`/`"22"`/`"24"` — STRING) |
|
|
314
|
+
| `health` | bool \| string \| object | No | TCP | All except cron |
|
|
315
|
+
| `env` | map (string values) | No | — | All (reserved names forbidden) |
|
|
316
|
+
| `schedule` | string (cron) | Required for cron | — | cron only |
|
|
317
|
+
| `run` | string | No | `job` | cron only (`job` or an HTTP URL) |
|
|
318
|
+
| `timezone` | string | No | `UTC` | cron only (`UTC`/`EST`/`PST`) |
|
|
319
|
+
| `depends_on` | string[] | No | — | All except cron (no self/circular, no cron) |
|
|
320
|
+
| `persist` | bool \| array | No | — | All except cron |
|
|
321
|
+
| `needs` | object | No | — | All (overrides/extends app-level per-key) |
|
|
322
|
+
|
|
323
|
+
### `path` — HTTP routing
|
|
324
|
+
|
|
325
|
+
`"/"` serves at root; `"/api"` under a prefix; `false` = internal only (no public
|
|
326
|
+
URL); omitted = treated as `false` with a soft warning. **Only one service can
|
|
327
|
+
claim `"/"`.**
|
|
328
|
+
|
|
329
|
+
### `source` — custom source location
|
|
330
|
+
|
|
331
|
+
```yaml
|
|
332
|
+
source:
|
|
333
|
+
path: src/worker # relative to project root; no leading /, no .., no globs
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Use `.` for a flat layout where the service code lives at the project root:
|
|
337
|
+
|
|
338
|
+
```yaml
|
|
339
|
+
services:
|
|
340
|
+
web:
|
|
341
|
+
type: node
|
|
342
|
+
path: /
|
|
343
|
+
source:
|
|
344
|
+
path: .
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### `build` — static site build step (static only)
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
build:
|
|
351
|
+
command: npm run build # required
|
|
352
|
+
output: dist # built output dir (default: dist)
|
|
353
|
+
env:
|
|
354
|
+
VITE_API_URL: https://api.example.com
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### `health` — probe config
|
|
358
|
+
|
|
359
|
+
Omitted / `false` → TCP probe. `true` → HTTP at `/health`. `"/healthz"` → HTTP at
|
|
360
|
+
that path. `{ path, expected }` → HTTP with expected status
|
|
361
|
+
(`healthy` (default, 2xx/3xx), `<500`, `200`, `2xx`).
|
|
362
|
+
|
|
363
|
+
### `env` — static environment variables
|
|
364
|
+
|
|
365
|
+
String key-values injected at runtime (not build). **Reserved names (rejected):**
|
|
366
|
+
`PORT`, `APP_NAME`, `SERVICE_NAME`, `NODE_ENV`, `MONGODB_URL`, `REDIS_URL`,
|
|
367
|
+
`AI_GATEWAY_URL`, `N8N_WEBHOOK_URL`, and any key starting with `CLOUDGRID_`. Use
|
|
368
|
+
`grid secrets set` for sensitive values.
|
|
369
|
+
|
|
370
|
+
### Per-service `needs` override
|
|
371
|
+
|
|
372
|
+
Merge is per-key — service values override app-level for that key; absent keys
|
|
373
|
+
inherit:
|
|
374
|
+
|
|
375
|
+
```yaml
|
|
376
|
+
needs: # App-level
|
|
377
|
+
database: true
|
|
378
|
+
cache: true
|
|
379
|
+
pubsub: true
|
|
380
|
+
services:
|
|
381
|
+
worker:
|
|
382
|
+
type: node
|
|
383
|
+
path: false
|
|
384
|
+
needs:
|
|
385
|
+
queue: true # Added
|
|
386
|
+
pubsub: false # Overridden (opted out)
|
|
387
|
+
# Effective: database: true, cache: true, queue: true, pubsub: false
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 5. Needs (infrastructure) — the nine needs
|
|
393
|
+
|
|
394
|
+
The `needs:` block declares infrastructure capabilities; the platform provisions
|
|
395
|
+
them and injects connection details as env vars. **Cron is NOT a need — it is a
|
|
396
|
+
service type.**
|
|
397
|
+
|
|
398
|
+
| Need | Provides | Injected env var(s) | Backing |
|
|
399
|
+
|------|----------|---------------------|---------|
|
|
400
|
+
| `database` | Primary datastore | `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) | MongoDB |
|
|
401
|
+
| `cache` | Key-value store, LRU eviction | `CACHE_REDIS_URL` (+legacy `REDIS_URL`) | Redis |
|
|
402
|
+
| `kv` | Key-value store, no eviction | `KV_REDIS_URL` | Redis |
|
|
403
|
+
| `queue` | Durable job queue substrate | `QUEUE_REDIS_URL` | Redis |
|
|
404
|
+
| `pubsub` | Broadcast pub/sub | `PUBSUB_REDIS_URL` | Redis |
|
|
405
|
+
| `vector` | Vector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | pgvector (Postgres) |
|
|
406
|
+
| `object_storage` | Object/blob bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | GCS |
|
|
407
|
+
| `disk` | Persistent filesystem mount | `DISK_PATH` | PVC |
|
|
408
|
+
| `ai` | AI Gateway access | `AI_GATEWAY_URL` | CloudGrid AI Gateway |
|
|
409
|
+
|
|
410
|
+
`cache`, `kv`, `queue`, `pubsub` all share one physical Redis but each gets a
|
|
411
|
+
distinct env var. `cache` runs `allkeys-lru` eviction (derived data); the other
|
|
412
|
+
three run `noeviction` (durable data).
|
|
413
|
+
|
|
414
|
+
### Usage forms
|
|
415
|
+
|
|
416
|
+
```yaml
|
|
417
|
+
needs:
|
|
418
|
+
database: true # Grid decides tier
|
|
419
|
+
database: pool # Shared Mongo (free)
|
|
420
|
+
database: dedicated # Entity-private Mongo
|
|
421
|
+
database: { tier: external, secret: MY_DB } # Bring your own (value from vault)
|
|
422
|
+
|
|
423
|
+
cache: true # Platform Redis
|
|
424
|
+
cache: { engine: external, secret: MY_REDIS } # Bring your own (same forms for kv/queue/pubsub)
|
|
425
|
+
|
|
426
|
+
vector: true # Platform default engine
|
|
427
|
+
vector: pgvector # pgvector explicitly
|
|
428
|
+
vector: { engine: pgvector, dim: 1536 } # With dimension hint
|
|
429
|
+
|
|
430
|
+
object_storage: true # Default size (100MB)
|
|
431
|
+
object_storage: { size: 10Gi } # Custom size
|
|
432
|
+
|
|
433
|
+
disk: true # 50MB at /data
|
|
434
|
+
disk: { size: 1Gi, mount: /var/data } # Custom (forbidden mounts: /proc, /sys, /dev)
|
|
435
|
+
|
|
436
|
+
ai: true # Enable AI Gateway (ai: false = disabled, default)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Injected environment variables
|
|
440
|
+
|
|
441
|
+
**Always injected** (do not set these yourself):
|
|
442
|
+
|
|
443
|
+
| Variable | Description |
|
|
444
|
+
|----------|-------------|
|
|
445
|
+
| `PORT` | Port your service should listen on (your `port:` or 8080). |
|
|
446
|
+
| `APP_NAME` | Entity slug. |
|
|
447
|
+
| `SERVICE_NAME` | Service name (the key under `services:`). |
|
|
448
|
+
| `NODE_ENV` | `production` in deployed environments. |
|
|
449
|
+
|
|
450
|
+
**Per `needs:` declaration:** as in the table above. Legacy aliases
|
|
451
|
+
(`MONGODB_URL`, `REDIS_URL`, `PGVECTOR_URL`) are injected alongside the canonical
|
|
452
|
+
names during the transition; new apps should read the canonical names
|
|
453
|
+
(`DATABASE_MONGODB_URL`, etc.) with the legacy name as a fallback.
|
|
454
|
+
|
|
455
|
+
### Persistence (disk)
|
|
456
|
+
|
|
457
|
+
Two ways to declare filesystem persistence — use `needs.disk` (recommended) or
|
|
458
|
+
service-level `persist:` (fine-grained). Do not mix the entity-level flag form and
|
|
459
|
+
the service-level verbose array form.
|
|
460
|
+
|
|
461
|
+
```yaml
|
|
462
|
+
# Recommended:
|
|
463
|
+
needs:
|
|
464
|
+
disk: { size: 1Gi, mount: /var/sqlite }
|
|
465
|
+
|
|
466
|
+
# Fine-grained, per-service:
|
|
467
|
+
services:
|
|
468
|
+
web:
|
|
469
|
+
type: node
|
|
470
|
+
path: /
|
|
471
|
+
persist:
|
|
472
|
+
- name: db
|
|
473
|
+
path: /data/sqlite
|
|
474
|
+
size: 1Gi
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Persistence pins the service to 1 replica with a `Recreate` rollout strategy
|
|
478
|
+
(single-writer constraint).
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## 6. Validation rules (the ones agents trip on)
|
|
483
|
+
|
|
484
|
+
The platform validates `cloudgrid.yaml` at plug time — strictly.
|
|
485
|
+
|
|
486
|
+
1. **No `Dockerfile`.** Images are generated from `services.*.type`; a Dockerfile
|
|
487
|
+
is silently ignored.
|
|
488
|
+
2. **No `org:` or `entity_id:` in the YAML.** Identity lives in
|
|
489
|
+
`.cloudgrid/link.json` (gitignored), not the manifest.
|
|
490
|
+
3. **No mixing `requires:` and `needs:`.** Use one or the other — the validator
|
|
491
|
+
rejects the combination. Author `needs:` (canonical); `requires:` is the
|
|
492
|
+
deprecated v1 alias.
|
|
493
|
+
4. **No mixed persistence forms.** Entity-level flag form and service-level
|
|
494
|
+
verbose array form cannot coexist.
|
|
495
|
+
5. **Unknown fields are rejected** with a "did you mean?" suggestion.
|
|
496
|
+
6. **Reserved env var names cannot be set** in `services.*.env` (see §4 `env`).
|
|
497
|
+
7. **`port` must be an integer 1-65535** (not a float). Forbidden on `type: cron`.
|
|
498
|
+
8. **`depends_on`:** no self-references, no circular dependencies, no references to
|
|
499
|
+
cron services.
|
|
500
|
+
9. **`node_version` must be a string** (`"22"`, not `22`).
|
|
501
|
+
10. **Cron services require `schedule`** and cannot have `path` or `port`.
|
|
502
|
+
11. **Entity names** must match `^[a-z][a-z0-9-]*[a-z0-9]$` (2-42 chars) — no
|
|
503
|
+
leading/trailing hyphens.
|
|
504
|
+
|
|
505
|
+
### File layout
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
my-app/
|
|
509
|
+
├── cloudgrid.yaml
|
|
510
|
+
├── .cloudgrid/link.json # gitignored; local binding to the server-side entity
|
|
511
|
+
├── services/
|
|
512
|
+
│ ├── web/ # service "web" → services/web/
|
|
513
|
+
│ │ ├── package.json
|
|
514
|
+
│ │ └── src/index.js
|
|
515
|
+
│ └── worker/
|
|
516
|
+
│ ├── package.json
|
|
517
|
+
│ └── src/index.js
|
|
518
|
+
└── .gitignore # includes .cloudgrid/
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Use `source.path: .` for a flat project where the code lives at the root.
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 7. `needs:` vs `requires:` — and the live deploy mechanics (READ THIS)
|
|
526
|
+
|
|
527
|
+
Bake these into any yaml you author:
|
|
528
|
+
|
|
529
|
+
- **`needs:` is canonical and it injects.** `needs: {database: true}` provisions
|
|
530
|
+
Mongo and injects the connection env vars — `DATABASE_MONGODB_URL` plus the
|
|
531
|
+
legacy `MONGODB_URL` alias. Likewise `cache: true` → `CACHE_REDIS_URL` (+legacy
|
|
532
|
+
`REDIS_URL`), `vector: pgvector` → `VECTOR_PGVECTOR_URL` (+legacy
|
|
533
|
+
`PGVECTOR_URL`), and so on across the nine needs (see §5). An app reading the
|
|
534
|
+
legacy names keeps working because the aliases are injected alongside.
|
|
535
|
+
(*Historical note: the deployer once did not inject from `needs:` — platform
|
|
536
|
+
issue #1527, now fixed and verified live. The old `requires:` workaround is
|
|
537
|
+
obsolete.*)
|
|
538
|
+
- **`requires:` is the deprecated v1 alias** — `requires: [mongodb]` /
|
|
539
|
+
`requires: [redis]`. The CLI warns it's deprecated. **Don't author new yaml with
|
|
540
|
+
it**; use `needs:` instead.
|
|
541
|
+
- **`needs:` and `requires:` together are HARD-REJECTED** by the validator ("use
|
|
542
|
+
one or the other"). So author **`needs:`-only** — never set both.
|
|
543
|
+
- **Static single-file pages deploy as inspirations** via `gridctl_drop` — instant,
|
|
544
|
+
synchronous, works on the hosted edition and any client.
|
|
545
|
+
- **A `cloudgrid.yaml` with `services:` makes it a runtime** — async build,
|
|
546
|
+
**local edition only** (Claude Desktop/Code or the CLI, not hosted). `plug`
|
|
547
|
+
returns `status: building`; poll status until live, then use the returned live
|
|
548
|
+
URL. Re-plug the same entity to update the same URL.
|
|
549
|
+
- **Read injected env vars lazily** (inside a getter/handler), never at module top
|
|
550
|
+
level — a top-level read of `DATABASE_MONGODB_URL` fails `next build`, which
|
|
551
|
+
imports the module for route analysis before the grid injects the var.
|
|
552
|
+
|
|
553
|
+
The canonical DB shape (mirrors the `app-with-data` template):
|
|
554
|
+
|
|
555
|
+
```yaml
|
|
556
|
+
name: my-app
|
|
557
|
+
services:
|
|
558
|
+
web:
|
|
559
|
+
type: nextjs
|
|
560
|
+
path: /
|
|
561
|
+
needs:
|
|
562
|
+
database: true
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
*This doc is the agent-facing distribution of the platform's canonical
|
|
568
|
+
`cloudgrid-yaml-reference.md`. See also `gridctl_fetch("doc", "capability-map")`
|
|
569
|
+
for matching a user intent to a template + the `needs:` injection status.*
|
|
@@ -2,32 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
A filled reference imitating the `app-with-data` template: a persistent team
|
|
4
4
|
task board. Same stack (Next.js App Router + the `mongodb` driver, grid-shared
|
|
5
|
-
Mongo via `process.env.
|
|
6
|
-
template — tasks have an `assignee` and a `status` (todo / doing / done),
|
|
7
|
-
the API supports updating status via PATCH.
|
|
5
|
+
Mongo via `process.env.DATABASE_MONGODB_URL`), slightly richer than the bare
|
|
6
|
+
todo template — tasks have an `assignee` and a `status` (todo / doing / done),
|
|
7
|
+
and the API supports updating status via PATCH.
|
|
8
8
|
|
|
9
9
|
Data persists in the grid-shared Mongo, so the whole team sees the same board
|
|
10
10
|
across sessions and refresh.
|
|
11
11
|
|
|
12
12
|
**Same three proven rules as the template:** (1) app code lives under
|
|
13
13
|
**`services/web/`** (the service name, NOT the repo root — `path:` is the URL
|
|
14
|
-
mount); (2)
|
|
15
|
-
module top level (a top-level read fails `next build`); (3)
|
|
16
|
-
declared with **`
|
|
17
|
-
injects `
|
|
14
|
+
mount); (2) the DB connection string is read **lazily inside the `getDb`
|
|
15
|
+
getter**, never at module top level (a top-level read fails `next build`); (3)
|
|
16
|
+
the datastore is declared with the canonical **`needs: { database: true }`** —
|
|
17
|
+
the deployer provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the
|
|
18
|
+
legacy `MONGODB_URL` alias). Don't use the deprecated `requires:` alias.
|
|
18
19
|
|
|
19
20
|
## cloudgrid.yaml
|
|
20
21
|
|
|
21
22
|
```yaml
|
|
22
|
-
#
|
|
23
|
-
#
|
|
23
|
+
# `needs: { database: true }` is canonical: the deployer provisions Mongo and
|
|
24
|
+
# injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL alias). `requires:`
|
|
25
|
+
# is the deprecated v1 alias — don't mix the two (the validator rejects it).
|
|
24
26
|
name: team-task-board
|
|
25
27
|
services:
|
|
26
28
|
web:
|
|
27
29
|
type: nextjs
|
|
28
30
|
path: /
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
needs:
|
|
32
|
+
database: true
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
## services/web/package.json
|
|
@@ -50,16 +52,18 @@ requires:
|
|
|
50
52
|
## services/web/lib/db.js
|
|
51
53
|
|
|
52
54
|
```js
|
|
53
|
-
// Cached Mongo client.
|
|
54
|
-
//
|
|
55
|
+
// Cached Mongo client. The grid injects DATABASE_MONGODB_URL (plus the legacy
|
|
56
|
+
// MONGODB_URL alias) at dev + runtime. Read the canonical var first, fall back to
|
|
57
|
+
// the legacy alias. Resolve it LAZILY inside the getter — a top-level read fails
|
|
58
|
+
// `next build`.
|
|
55
59
|
import { MongoClient } from "mongodb";
|
|
56
60
|
|
|
57
61
|
function clientPromise() {
|
|
58
|
-
const uri = process.env.MONGODB_URL;
|
|
62
|
+
const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
|
|
59
63
|
if (!uri) {
|
|
60
64
|
throw new Error(
|
|
61
|
-
"
|
|
62
|
-
"deploy with `grid plug`. Do not set it by hand.",
|
|
65
|
+
"DATABASE_MONGODB_URL is not set. The grid injects it — run `grid dev` " +
|
|
66
|
+
"locally or deploy with `grid plug`. Do not set it by hand.",
|
|
63
67
|
);
|
|
64
68
|
}
|
|
65
69
|
if (!globalThis.__mongoClientPromise) {
|
|
@@ -244,8 +248,9 @@ export default function Board({ initialTasks }) {
|
|
|
244
248
|
|
|
245
249
|
- Deploy the same way as the template: `grid dev` locally, `grid plug` to deploy
|
|
246
250
|
(async — poll status to a live URL). Re-plug the same entity to keep one URL.
|
|
247
|
-
- The only store this needs is Mongo (`
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
- The only store this needs is Mongo (`needs: { database: true }` — the deployer
|
|
252
|
+
provisions Mongo and injects `DATABASE_MONGODB_URL`, plus the legacy
|
|
253
|
+
`MONGODB_URL` alias). Add `cache: true` to `needs:` only for
|
|
254
|
+
caching/sessions/pubsub you actually use.
|
|
250
255
|
- All app code lives under `services/web/`; the connection is read lazily inside
|
|
251
256
|
`getDb`, never at module top level.
|
|
@@ -7,23 +7,26 @@ page whose state is in memory only.
|
|
|
7
7
|
## How the grid gives you a database
|
|
8
8
|
|
|
9
9
|
You do **not** provision a database or set a connection string. In
|
|
10
|
-
`cloudgrid.yaml` you declare `
|
|
10
|
+
`cloudgrid.yaml` you declare `needs: { database: true }`, and the grid:
|
|
11
11
|
|
|
12
12
|
- provisions shared Mongo for the app, and
|
|
13
|
-
- injects the connection string as the **`
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
>
|
|
26
|
-
>
|
|
13
|
+
- injects the connection string as the **`DATABASE_MONGODB_URL`** environment
|
|
14
|
+
variable (plus the legacy `MONGODB_URL` alias) — at dev-time (`grid dev`) and
|
|
15
|
+
at runtime (after `grid plug`).
|
|
16
|
+
|
|
17
|
+
The app reads it via `process.env.DATABASE_MONGODB_URL` (with a legacy
|
|
18
|
+
`process.env.MONGODB_URL` fallback) in `services/web/lib/db.js` — **lazily,
|
|
19
|
+
inside the `getDb` getter, never at module top level** (a top-level read fails
|
|
20
|
+
`next build`, which imports the module for route analysis before the grid injects
|
|
21
|
+
the var). Never hardcode a connection string; never commit a secret. (If you also
|
|
22
|
+
declare `cache: true`, the grid injects `CACHE_REDIS_URL` — and the legacy
|
|
23
|
+
`REDIS_URL` alias — the same way.)
|
|
24
|
+
|
|
25
|
+
> **Declare `needs: { database: true }` (the canonical shape).** The deployer
|
|
26
|
+
> provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy
|
|
27
|
+
> `MONGODB_URL` alias). `requires:` is the deprecated v1 alias — don't author new
|
|
28
|
+
> yaml with it, and never set `needs:` and `requires:` together (the validator
|
|
29
|
+
> rejects the combination).
|
|
27
30
|
|
|
28
31
|
## Service layout
|
|
29
32
|
|
|
@@ -36,7 +39,7 @@ App code lives under **`services/web/`**, not the template root. `path:` in
|
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
41
|
npm install
|
|
39
|
-
grid dev # runs Next.js with
|
|
42
|
+
grid dev # runs Next.js with DATABASE_MONGODB_URL injected against dev Mongo
|
|
40
43
|
```
|
|
41
44
|
|
|
42
45
|
## Deploy
|
|
@@ -52,9 +55,9 @@ to update the same URL.
|
|
|
52
55
|
## File tree
|
|
53
56
|
|
|
54
57
|
```
|
|
55
|
-
cloudgrid.yaml # name + services.web (nextjs) +
|
|
58
|
+
cloudgrid.yaml # name + services.web (nextjs) + needs: { database: true }
|
|
56
59
|
services/web/package.json # next, react, react-dom, mongodb driver only
|
|
57
|
-
services/web/lib/db.js # lazy Mongo client from
|
|
60
|
+
services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
|
|
58
61
|
services/web/app/layout.js # root layout + inline CSS
|
|
59
62
|
services/web/app/page.js # server component: reads todos from Mongo
|
|
60
63
|
services/web/app/todo-form.js # client form: POST/DELETE via the API
|
|
@@ -67,4 +70,4 @@ services/web/app/api/todos/route.js # GET (list) / POST (add) / DELETE (remove
|
|
|
67
70
|
`services/web/app/page.js` (e.g. `submissions`, `tasks`, `entries`).
|
|
68
71
|
- Change the document fields (add owners, timestamps, statuses).
|
|
69
72
|
- Add more routes/collections as the app grows.
|
|
70
|
-
- Add `
|
|
73
|
+
- Add `cache: true` to `needs:` only if you actually need Redis.
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
# Rename this app. The grid injects
|
|
2
|
-
# as environment variables at runtime — do NOT set them
|
|
3
|
-
# a connection string or secret.
|
|
1
|
+
# Rename this app. The grid injects the DB connection string (and a Redis URL if
|
|
2
|
+
# you add a cache) as environment variables at runtime — do NOT set them
|
|
3
|
+
# yourself, and never commit a connection string or secret.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# `
|
|
8
|
-
#
|
|
5
|
+
# `needs: { database: true }` is the canonical, recommended shape. The deployer
|
|
6
|
+
# provisions Mongo and injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL
|
|
7
|
+
# alias). `requires:` is the deprecated v1 alias — don't author new yaml with it,
|
|
8
|
+
# and never set `needs:` and `requires:` together (the validator rejects it).
|
|
9
9
|
name: my-app
|
|
10
10
|
services:
|
|
11
11
|
web:
|
|
12
12
|
type: nextjs
|
|
13
13
|
path: /
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# needs:
|
|
17
|
-
# database: true
|
|
18
|
-
requires:
|
|
19
|
-
- mongodb
|
|
14
|
+
needs:
|
|
15
|
+
database: true
|
|
@@ -9,17 +9,19 @@ so it survives refresh and is shared across sessions — unlike a static page.
|
|
|
9
9
|
root.** `path:` in `cloudgrid.yaml` is the URL mount, NOT the filesystem path.
|
|
10
10
|
The service named `web` → the CLI looks for `services/web/`. App files at the
|
|
11
11
|
root fail with `Error: Service directory not found: …/services/web`.
|
|
12
|
-
2. **Read
|
|
13
|
-
The grid injects
|
|
14
|
-
variable at dev-time and runtime; the
|
|
15
|
-
|
|
12
|
+
2. **Read the DB connection string LAZILY (inside the getter), never at module
|
|
13
|
+
top level.** The grid injects it as the `DATABASE_MONGODB_URL` environment
|
|
14
|
+
variable (plus the legacy `MONGODB_URL` alias) at dev-time and runtime; the
|
|
15
|
+
app reads `process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL` inside
|
|
16
|
+
`getDb`. A top-level `const uri = process.env.DATABASE_MONGODB_URL; if (!uri)
|
|
16
17
|
throw` fails `next build` (the module is imported for route analysis before
|
|
17
18
|
the grid injects the var). Never hardcode a connection string; never commit a
|
|
18
19
|
secret.
|
|
19
|
-
3. **Declare the datastore with `
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
3. **Declare the datastore with `needs: { database: true }`.** This is the
|
|
21
|
+
canonical shape — the deployer provisions Mongo and injects
|
|
22
|
+
`DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias). `requires:` is
|
|
23
|
+
the deprecated v1 alias; don't author new yaml with it, and never set `needs:`
|
|
24
|
+
and `requires:` together (the validator rejects the combination).
|
|
23
25
|
|
|
24
26
|
Write these files into the scaffolded app folder — the app code goes under
|
|
25
27
|
`services/web/` — adapt the collection/fields to the user's app, then `grid dev`
|
|
@@ -28,9 +30,9 @@ Write these files into the scaffolded app folder — the app code goes under
|
|
|
28
30
|
## File tree
|
|
29
31
|
|
|
30
32
|
```
|
|
31
|
-
cloudgrid.yaml # name + services.web (nextjs) +
|
|
33
|
+
cloudgrid.yaml # name + services.web (nextjs) + needs: { database: true }
|
|
32
34
|
services/web/package.json # next, react, react-dom, mongodb driver only
|
|
33
|
-
services/web/lib/db.js # lazy Mongo client from
|
|
35
|
+
services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
|
|
34
36
|
services/web/app/layout.js # root layout + inline CSS
|
|
35
37
|
services/web/app/page.js # server component: reads todos from Mongo
|
|
36
38
|
services/web/app/todo-form.js # client form: POST/DELETE via the API
|
|
@@ -40,32 +42,28 @@ services/web/app/api/todos/route.js # GET (list) / POST (add) / DELETE (remo
|
|
|
40
42
|
## cloudgrid.yaml
|
|
41
43
|
|
|
42
44
|
```yaml
|
|
43
|
-
# Rename this app. The grid injects
|
|
44
|
-
# as environment variables at runtime — do NOT set them
|
|
45
|
-
# a connection string or secret.
|
|
45
|
+
# Rename this app. The grid injects the DB connection string (and a Redis URL if
|
|
46
|
+
# you add a cache) as environment variables at runtime — do NOT set them
|
|
47
|
+
# yourself, and never commit a connection string or secret.
|
|
46
48
|
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# `
|
|
50
|
-
#
|
|
49
|
+
# `needs: { database: true }` is the canonical, recommended shape. The deployer
|
|
50
|
+
# provisions Mongo and injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL
|
|
51
|
+
# alias). `requires:` is the deprecated v1 alias — don't author new yaml with it,
|
|
52
|
+
# and never set `needs:` and `requires:` together (the validator rejects it).
|
|
51
53
|
name: my-app
|
|
52
54
|
services:
|
|
53
55
|
web:
|
|
54
56
|
type: nextjs
|
|
55
57
|
path: /
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# needs:
|
|
59
|
-
# database: true
|
|
60
|
-
requires:
|
|
61
|
-
- mongodb
|
|
58
|
+
needs:
|
|
59
|
+
database: true
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
> **Capability:** this template's
|
|
65
|
-
>
|
|
66
|
-
> `
|
|
67
|
-
>
|
|
68
|
-
>
|
|
62
|
+
> **Capability:** this template's need is `database: true`. The deployer
|
|
63
|
+
> provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy
|
|
64
|
+
> `MONGODB_URL` alias), so an app reading either var works. `requires:` is the
|
|
65
|
+
> deprecated v1 alias — don't mix it with `needs:` (the validator rejects the
|
|
66
|
+
> combination). See the capability-map for the full injection table.
|
|
69
67
|
|
|
70
68
|
## services/web/package.json
|
|
71
69
|
|
|
@@ -93,22 +91,23 @@ requires:
|
|
|
93
91
|
```js
|
|
94
92
|
// Cached MongoDB client for the App Router.
|
|
95
93
|
//
|
|
96
|
-
// The grid injects the connection string as the
|
|
97
|
-
// at runtime (after `grid plug`) and
|
|
98
|
-
//
|
|
94
|
+
// The grid injects the connection string as the DATABASE_MONGODB_URL environment
|
|
95
|
+
// variable (plus the legacy MONGODB_URL alias) at runtime (after `grid plug`) and
|
|
96
|
+
// under `grid dev` locally. Read the canonical var first, fall back to the legacy
|
|
97
|
+
// alias. Never hardcode a connection string here and never commit a secret.
|
|
99
98
|
//
|
|
100
99
|
// The env var and client are resolved LAZILY (inside getDb), never at module top
|
|
101
100
|
// level — otherwise `next build` throws when it imports this module for route
|
|
102
|
-
// analysis, before the grid injects
|
|
101
|
+
// analysis, before the grid injects the var.
|
|
103
102
|
import { MongoClient } from "mongodb";
|
|
104
103
|
|
|
105
104
|
function clientPromise() {
|
|
106
|
-
const uri = process.env.MONGODB_URL;
|
|
105
|
+
const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
|
|
107
106
|
if (!uri) {
|
|
108
107
|
throw new Error(
|
|
109
|
-
"
|
|
110
|
-
"with `grid dev` locally, or deploy it with `grid plug` (the grid
|
|
111
|
-
"
|
|
108
|
+
"DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
|
|
109
|
+
"this app with `grid dev` locally, or deploy it with `grid plug` (the grid " +
|
|
110
|
+
"injects the DB connection string at runtime). Do not set it by hand.",
|
|
112
111
|
);
|
|
113
112
|
}
|
|
114
113
|
if (!globalThis.__mongoClientPromise) {
|
|
@@ -119,7 +118,7 @@ function clientPromise() {
|
|
|
119
118
|
|
|
120
119
|
export async function getDb() {
|
|
121
120
|
const client = await clientPromise();
|
|
122
|
-
// The default DB comes from the
|
|
121
|
+
// The default DB comes from the connection-string path segment the grid injects.
|
|
123
122
|
return client.db();
|
|
124
123
|
}
|
|
125
124
|
```
|
|
@@ -267,5 +266,5 @@ export async function DELETE(request) {
|
|
|
267
266
|
|
|
268
267
|
- Rename the `todos` collection to your data (`submissions`, `tasks`, `entries`).
|
|
269
268
|
- Change the document fields; add owners/timestamps/statuses.
|
|
270
|
-
- Add `
|
|
269
|
+
- Add `cache: true` to `needs:` only if you actually need Redis.
|
|
271
270
|
- Run `grid dev` to test locally, `grid plug` to deploy (async — poll to live).
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
// Cached MongoDB client for the App Router.
|
|
2
2
|
//
|
|
3
|
-
// The grid injects the connection string as the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// The grid injects the connection string as the DATABASE_MONGODB_URL environment
|
|
4
|
+
// variable (plus the legacy MONGODB_URL alias) at dev-time (`grid dev`) and
|
|
5
|
+
// runtime (after `grid plug`). Read the canonical var first, fall back to the
|
|
6
|
+
// legacy alias. Never hardcode a connection string here and never commit a secret.
|
|
6
7
|
//
|
|
7
8
|
// The env var and client are resolved LAZILY (inside getDb), never at module top
|
|
8
9
|
// level — otherwise `next build` throws when it imports this module for route
|
|
9
|
-
// analysis, before the grid injects
|
|
10
|
+
// analysis, before the grid injects the var.
|
|
10
11
|
import { MongoClient } from "mongodb";
|
|
11
12
|
|
|
12
13
|
function clientPromise() {
|
|
13
|
-
const uri = process.env.MONGODB_URL;
|
|
14
|
+
const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
|
|
14
15
|
if (!uri) {
|
|
15
16
|
throw new Error(
|
|
16
|
-
"
|
|
17
|
-
"with `grid dev` locally, or deploy it with `grid plug` (the grid
|
|
18
|
-
"
|
|
17
|
+
"DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
|
|
18
|
+
"this app with `grid dev` locally, or deploy it with `grid plug` (the grid " +
|
|
19
|
+
"injects the DB connection string at runtime). Do not set it by hand.",
|
|
19
20
|
);
|
|
20
21
|
}
|
|
21
22
|
if (!globalThis.__mongoClientPromise) {
|
|
@@ -26,6 +27,6 @@ function clientPromise() {
|
|
|
26
27
|
|
|
27
28
|
export async function getDb() {
|
|
28
29
|
const client = await clientPromise();
|
|
29
|
-
// The default DB comes from the
|
|
30
|
+
// The default DB comes from the connection-string path segment the grid injects.
|
|
30
31
|
return client.db();
|
|
31
32
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: persistent-apps
|
|
3
|
-
when: A persistent (Mongo-backed) app misbehaves — data doesn't survive refresh,
|
|
4
|
-
summary: Symptom, cause, and fix for the common app-with-data (runtime Mongo app) problems — in-memory vs persistent, injected MONGODB_URL, async deploys, and the hosted-edition gate.
|
|
3
|
+
when: A persistent (Mongo-backed) app misbehaves — data doesn't survive refresh, DATABASE_MONGODB_URL is undefined, a runtime deploy seems stuck, or it won't build on hosted.
|
|
4
|
+
summary: Symptom, cause, and fix for the common app-with-data (runtime Mongo app) problems — in-memory vs persistent, injected DATABASE_MONGODB_URL (+legacy MONGODB_URL), async deploys, and the hosted-edition gate.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Troubleshooting: persistent apps (app-with-data)
|
|
@@ -12,11 +12,11 @@ Match the symptom to the row, apply the fix, and stop.
|
|
|
12
12
|
| --- | --- | --- |
|
|
13
13
|
| Data doesn't persist / resets on refresh | You built a static inspiration (in-memory / localStorage), not a runtime app | Use the `app-with-data` workflow: a real Next.js + Mongo runtime that reads `process.env.MONGODB_URL`. Static templates keep state only in memory. |
|
|
14
14
|
| `Error: Service directory not found: …/services/web` | App code lives at the repo/template root, not under the service folder | App code must live under `services/<name>/` (e.g. `services/web/`). `path:` in `cloudgrid.yaml` is the URL mount, NOT the filesystem path — the service name is the folder the CLI looks for. |
|
|
15
|
-
| `next build` fails /
|
|
16
|
-
| App builds + deploys but every request 500s with `
|
|
17
|
-
| `
|
|
15
|
+
| `next build` fails / DB connection error at build time | The DB env var is read at module top level; `next build` imports the module for route analysis before the grid injects the var | Read `process.env.DATABASE_MONGODB_URL` (with a legacy `MONGODB_URL` fallback) LAZILY inside the getter (e.g. inside `getDb`), never at module load. Moving the check into the function lets the build pass. |
|
|
16
|
+
| App builds + deploys but every request 500s with `DATABASE_MONGODB_URL not set` | The datastore isn't declared, or you declared the deprecated `requires:` instead of `needs:` | Declare `needs: { database: true }` in `cloudgrid.yaml`. The deployer provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias). Never set `needs:` and `requires:` together (the validator rejects it). |
|
|
17
|
+
| `DATABASE_MONGODB_URL is undefined` (or the db.js guard throws) | The env var isn't present because you're not running under the grid | Run locally with `grid dev`, or deploy with `grid plug` — the grid injects `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) at dev-time and runtime. Do NOT set it yourself and never hardcode a connection string. |
|
|
18
18
|
| Deploy seems stuck / no URL returned | Runtime deploys are asynchronous | `plug` returns `status: building`, not a URL. Poll `gridctl_status` (or the poll_url) until the app is live, then return the live URL. Don't treat the build/log link as the deliverable. |
|
|
19
19
|
| It won't build on the hosted edition | Hosted MCP is inline-only — it can't run the CLI or folder-plug | Runtime apps need the LOCAL edition (Claude Desktop/Code) or the CLI. On hosted, tell the user persistence isn't available there and offer a static version instead. |
|
|
20
|
-
| Redis not available | `
|
|
20
|
+
| Redis not available | `cache` wasn't declared | Add `cache: true` to `needs:` in `cloudgrid.yaml`; the grid then injects `CACHE_REDIS_URL` (plus the legacy `REDIS_URL` alias). Only add it if the app actually needs Redis. |
|
|
21
21
|
|
|
22
22
|
See the `app-with-data` workflow for the full recipe.
|
|
@@ -4,8 +4,8 @@ when: to-do, task list, notes app, guestbook, CRUD app, form that SAVES/STORES s
|
|
|
4
4
|
needs: database
|
|
5
5
|
deploy: runtime
|
|
6
6
|
editions: local
|
|
7
|
-
capabilities_note: persistent — needs a database (Mongo). Runtime app, async build, local edition only.
|
|
8
|
-
summary: Build a real persistent Next.js + Mongo runtime app on the grid — edition-gate first, scaffold, put the app under services/web/, wire process.env.MONGODB_URL lazily, declare
|
|
7
|
+
capabilities_note: persistent — needs a database (Mongo). Runtime app, async build, local edition only. Declare the canonical `needs: { database: true }`; the deployer injects DATABASE_MONGODB_URL (+legacy MONGODB_URL).
|
|
8
|
+
summary: Build a real persistent Next.js + Mongo runtime app on the grid — edition-gate first, scaffold, put the app under services/web/, wire process.env.DATABASE_MONGODB_URL (legacy MONGODB_URL fallback) lazily, declare needs:{database:true} (not requires:), deploy async, poll to a live URL.
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Workflow: app-with-data
|
|
@@ -48,7 +48,7 @@ the entity + `.cloudgrid/link.json` and writes a `cloudgrid.yaml` with an EMPTY
|
|
|
48
48
|
`services: {}`. `plug` needs a linked directory, so run `init` FIRST.
|
|
49
49
|
|
|
50
50
|
You then do two things: (a) write the app under **`services/web/`**, and (b) fill
|
|
51
|
-
in `cloudgrid.yaml` to the shape below (`services.web` + `
|
|
51
|
+
in `cloudgrid.yaml` to the shape below (`services.web` + `needs: { database: true }`).
|
|
52
52
|
|
|
53
53
|
## 4. Add the datastore + wire Mongo
|
|
54
54
|
|
|
@@ -63,27 +63,28 @@ in `cloudgrid.yaml` to the shape below (`services.web` + `requires: [mongodb]`).
|
|
|
63
63
|
web:
|
|
64
64
|
type: nextjs
|
|
65
65
|
path: /
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
#
|
|
66
|
+
needs:
|
|
67
|
+
database: true
|
|
68
|
+
# cache: true # OPTIONAL — add only if the app needs Redis
|
|
69
69
|
```
|
|
70
|
-
**Declare the datastore with `
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
**Declare the datastore with `needs: { database: true }`** — this is the
|
|
71
|
+
canonical shape. The deployer provisions Mongo and injects
|
|
72
|
+
`DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias) at dev-time and
|
|
73
|
+
runtime. `requires:` is the deprecated v1 alias; don't author new yaml with it,
|
|
74
|
+
and never set `needs:` and `requires:` together (the validator rejects it).
|
|
75
75
|
2. Fetch the template for the Mongo wiring + CRUD shape:
|
|
76
76
|
`gridctl_fetch("template", "app-with-data")`. It is a minimal, real
|
|
77
77
|
Next.js + `mongodb`-driver to-do app under `services/web/`: a lazy client in
|
|
78
78
|
`services/web/lib/db.js`, an App-Router GET/POST/DELETE route on a `todos`
|
|
79
79
|
collection, and a page.
|
|
80
80
|
3. Adapt it to the user's app (rename the collection, change the fields, adjust
|
|
81
|
-
the UI). **Read the database from `process.env.
|
|
82
|
-
|
|
83
|
-
string; never commit a
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
module
|
|
81
|
+
the UI). **Read the database from `process.env.DATABASE_MONGODB_URL`** (falling
|
|
82
|
+
back to the legacy `process.env.MONGODB_URL`) — the grid injects those env vars
|
|
83
|
+
at dev-time and runtime. Never hardcode a connection string; never commit a
|
|
84
|
+
secret.
|
|
85
|
+
- **Put the DB connection behind a lazy getter — never read the connection
|
|
86
|
+
string at module top level, or `next build` fails** (the module is imported
|
|
87
|
+
for route analysis before the grid injects the var).
|
|
87
88
|
- (Optional) fetch `gridctl_fetch("example", "app-with-data")` for a slightly
|
|
88
89
|
richer filled reference to imitate.
|
|
89
90
|
|
|
@@ -96,7 +97,9 @@ and sanity-check before deploying. Don't require it.
|
|
|
96
97
|
|
|
97
98
|
- API keys / secrets → `gridctl_secrets`.
|
|
98
99
|
- Non-secret config → `gridctl_env`.
|
|
99
|
-
- Do **NOT** set
|
|
100
|
+
- Do **NOT** set the DB/cache connection vars yourself (`DATABASE_MONGODB_URL`,
|
|
101
|
+
`CACHE_REDIS_URL`, or their legacy `MONGODB_URL` / `REDIS_URL` aliases) — the
|
|
102
|
+
grid injects them.
|
|
100
103
|
|
|
101
104
|
## 7. Deploy (async)
|
|
102
105
|
|
package/src/tools.js
CHANGED
|
@@ -90,6 +90,7 @@ Operating rules:
|
|
|
90
90
|
10. Publishing a heavy or local file: in the local edition use the path parameter so it is read from disk (no inline size limit); never base64-encode HTML and never pass a file path (or an @-prefixed path) as html. If a drop looks empty, use gridctl_source to check what was actually published, then re-plug with the real HTML/path and target_entity_id.
|
|
91
91
|
11. Persistence check: if the user needs to SAVE data, share state across users/sessions, log in, or store submissions, that's a runtime app-with-data (Mongo-backed), NOT a static page — static templates keep state only in memory and lose it on refresh. Use the app-with-data workflow. This requires the LOCAL edition (Claude Desktop/Code or the CLI); on the hosted edition, tell the user persistence isn't available there and offer a static version.
|
|
92
92
|
12. To choose what to build: match the request against the workflow when: triggers and the capability-map (gridctl_fetch({kind:"doc", name:"capability-map"})). Pick the template whose needs: matches what the app requires (persistence → database; scheduled → cron; etc.). Static (needs: none) deploys as an inspiration (instant, any edition); anything needing needs: is a runtime (async, local edition).
|
|
93
|
+
13. Before writing a cloudgrid.yaml, fetch the reference: gridctl_fetch({kind:"doc", name:"cloudgrid-yaml"}) — it has the full schema, the needs: vocabulary, and the requires-vs-needs caveat (the deployer does not inject from needs: yet, so a database/cache today uses requires: [mongodb] / requires: [redis]).
|
|
93
94
|
|
|
94
95
|
Deploy is edition-dependent: on the hosted MCP call the drop tool with the HTML; on local MCP / CLI write the file and run the plug tool. An HTML page deploys synchronously, so you get a URL right away.`;
|
|
95
96
|
|