create-feltkit 0.3.1 → 0.3.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/README.md +14 -3
- package/bin/create.js +30 -10
- package/package.json +3 -2
- package/scaffold.d.ts +7 -2
- package/scaffold.js +45 -9
- package/template/README.md +251 -0
- package/template/feltkit/app.ts +22 -9
- package/template/package.json +6 -1
- package/template-managed/.dockerignore +7 -0
- package/template-managed/Dockerfile +24 -0
- package/template-managed/README.md +222 -0
- package/template-managed/_gitignore +7 -0
- package/template-managed/docker-compose.yml +17 -0
- package/template-managed/feltdb.contract.json +1102 -0
- package/template-managed/feltkit/_generated/api.ts +9 -0
- package/template-managed/feltkit/app.ts +40 -0
- package/template-managed/feltkit/items.ts +55 -0
- package/template-managed/feltkit/system.ts +14 -0
- package/template-managed/fly.toml +28 -0
- package/template-managed/package.json +35 -0
- package/template-managed/scripts/deploy-fly.mjs +54 -0
- package/template-managed/scripts/managed-env.mjs +103 -0
- package/template-managed/tests/config.test.ts +91 -0
- package/template-managed/tests/inventory.test.ts +69 -0
- package/template-managed/tests/managed.live.test.ts +75 -0
- package/template-managed/tsconfig.json +15 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Inventory on Managed FeltDB: a FeltKit example
|
|
2
|
+
|
|
3
|
+
A small inventory service: items with stock levels, and a movement recorded with every stock
|
|
4
|
+
change in the same FeltDB transaction. Its state lives in **Managed FeltDB**, the hosted FeltDB
|
|
5
|
+
service. Nothing on the app's host holds state, so you can restart or redeploy the app and
|
|
6
|
+
nothing is lost.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm create feltkit@latest inventory -- --template managed
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## How this differs from the default starter
|
|
13
|
+
|
|
14
|
+
| | Default starter (`--template tasks`) | This starter (`--template managed`) |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Where state lives | local FeltDB: `.feltkit/<env>/state`, or a FeltDB server you run (`FELTDB_URL` + `FELTDB_TOKEN`) | Managed FeltDB: one managed *application*, one *environment* (`production` by default) |
|
|
17
|
+
| Development and tests | local FeltDB | local FeltDB, the same (`npm test`, `npm run dev`) |
|
|
18
|
+
| What FeltDB stores | any collection | only what the promoted **contract** declares: `feltdb.contract.json`, generated by `feltkit contract` and checked in |
|
|
19
|
+
| AppPort Services (files, intelligence, …) | yes | no: their collections can't be declared in a contract yet, so `feltkit contract` refuses an app that enables them |
|
|
20
|
+
| Credentials | none for local state | a **runtime key** with four scopes (server-side only) and a **control key** for publishing contracts (never in the running app), both minted by the FeltDB CLI |
|
|
21
|
+
| Deployment | `feltkit deploy` (a Docker artifact with a state volume) | `Dockerfile`, `docker-compose.yml`, `fly.toml`: stateless, configured by the environment |
|
|
22
|
+
|
|
23
|
+
Managed FeltDB is an external service. Nothing here provisions or operates it: `feltdb
|
|
24
|
+
init-managed` creates *your application* in it, using your FeltDB account.
|
|
25
|
+
|
|
26
|
+
## What's here
|
|
27
|
+
|
|
28
|
+
| Path | What it is |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `feltkit/app.ts` | roles (`clerk`, `auditor`), collections `items` and `movements` |
|
|
31
|
+
| `feltkit/items.ts` | `list`, `get`, `create` and `adjust` (AppPort capabilities `items.create` and `stock.adjust`), `history` |
|
|
32
|
+
| `feltkit/system.ts` | `system:status`: an authenticated read through the configured backend; names the backend, never a key |
|
|
33
|
+
| `feltdb.contract.json` | the FeltDB application contract: your collections, FeltKit's records, FeltDB's agent state |
|
|
34
|
+
| `scripts/managed-env.mjs` | runs FeltKit with the runtime settings only; refuses missing or contradictory ones |
|
|
35
|
+
| `Dockerfile`, `.dockerignore`, `docker-compose.yml` | the image, and a way to run it locally against Managed FeltDB |
|
|
36
|
+
| `fly.toml`, `scripts/deploy-fly.mjs` | deployment to Fly.io |
|
|
37
|
+
| `tests/` | `inventory` (persistence and refusals, local), `config` (configuration contract, deploy script), `managed.live` (against Managed FeltDB) |
|
|
38
|
+
|
|
39
|
+
## Prerequisites
|
|
40
|
+
|
|
41
|
+
- Node.js 20.11 or later, and npm.
|
|
42
|
+
- A FeltDB account (`npm run managed:setup` creates one if the e-mail is new). The account
|
|
43
|
+
creates the application and its keys, so you need permission to manage keys for it. The
|
|
44
|
+
account service never issues a scope you don't hold.
|
|
45
|
+
- To deploy: Docker, or [flyctl](https://fly.io/docs/flyctl/install/) signed in to Fly.io.
|
|
46
|
+
|
|
47
|
+
## 1. Run it locally
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install
|
|
51
|
+
npm test # local FeltDB per test: persistence, refusals, roles, configuration
|
|
52
|
+
npm run dev # http://localhost:3000, state in .feltkit/development/state
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Development and tests always use local FeltDB, so you don't need an account for either.
|
|
56
|
+
|
|
57
|
+
## 2. Set up Managed FeltDB: account, application, keys, contract
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run managed:setup
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This runs `feltkit contract` (writes `feltdb.contract.json`), then FeltDB's own CLI (shipped with
|
|
64
|
+
`@feltdb/core`):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
feltdb init-managed --contract feltdb.contract.json --runtime-scopes minimal
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
It signs you in (or signs you up), prompting for your e-mail and password. For CI, set
|
|
71
|
+
`FELTDB_MANAGED_EMAIL` and `FELTDB_MANAGED_PASSWORD` instead. It then:
|
|
72
|
+
|
|
73
|
+
1. creates or reuses the managed application named after `package.json`'s `name` (`--name` to choose);
|
|
74
|
+
2. mints a **control key** (publish and promote contracts) and a **runtime key** with
|
|
75
|
+
`application:read`, `application:revision:read`, `state:read` and `state:write`, nothing more;
|
|
76
|
+
3. writes `.env.local` (mode 600) and `.feltdb/managed.json`; both are in `.gitignore`;
|
|
77
|
+
4. publishes and promotes the contract to `production`, and checks that the runtime key resolves it.
|
|
78
|
+
|
|
79
|
+
| Variable (`.env.local`) | Secret? | Used by |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `FELTDB_URL` | no | the app |
|
|
82
|
+
| `FELTDB_APPLICATION_ID` | no | the app |
|
|
83
|
+
| `FELTDB_ENVIRONMENT` | no | the app (it is also FeltKit's environment) |
|
|
84
|
+
| `FELTDB_TOKEN` | **yes**: the runtime key | the app |
|
|
85
|
+
| `FELTDB_MANAGED_CONTROL_API_KEY` | **yes**: the control key | `feltdb publish` only; never the app |
|
|
86
|
+
|
|
87
|
+
`init-managed` also writes `FELTDB_NAMESPACE`, `FELTDB_MODE` and `VITE_FELTDB_*`, which hold no
|
|
88
|
+
secrets. The app doesn't use them, and `scripts/managed-env.mjs` doesn't pass them on.
|
|
89
|
+
|
|
90
|
+
## 3. Run it against Managed FeltDB from your machine
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run managed:session -- --id ops --role clerk # prints a session token once
|
|
94
|
+
npm run managed:start # http://localhost:3000, state in Managed FeltDB
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Both go through `scripts/managed-env.mjs`, which reads `.env.local`. It passes FeltKit only the
|
|
98
|
+
four runtime settings and refuses to start if one is missing. `managed:start` prints
|
|
99
|
+
`State remote https://…`.
|
|
100
|
+
|
|
101
|
+
Production has no development sign-in, so callers use a session (`Authorization: Bearer <token>`)
|
|
102
|
+
or your own `auth.authenticate` hook in `feltkit/app.ts`. Create sessions while the server is
|
|
103
|
+
stopped (or from another machine). A running production server's admin API answers only verified
|
|
104
|
+
operators.
|
|
105
|
+
|
|
106
|
+
## 4. Verify reads and writes
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
TOKEN=fks_… # from managed:session
|
|
110
|
+
curl -s -X POST localhost:3000/api/mutation/items:create -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"args":{"sku":"BOLT-10","name":"M10 bolt","quantity":100}}'
|
|
111
|
+
curl -s -X POST localhost:3000/api/mutation/items:adjust -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"args":{"sku":"BOLT-10","delta":-30,"reason":"order 1"}}'
|
|
112
|
+
curl -s -X POST localhost:3000/api/query/items:list -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{}'
|
|
113
|
+
curl -s -X POST localhost:3000/api/query/system:status -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{}'
|
|
114
|
+
# → {"ok":true,"value":{"environment":"production","storage":{"kind":"remote","location":"https://api.feltdb.com","durable":true},"readable":true,…}}
|
|
115
|
+
curl -s localhost:3000/_feltkit/health # liveness: 200 once FeltKit reached FeltDB with its key at startup
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 5. Restart and check the state is still there
|
|
119
|
+
|
|
120
|
+
Stop the app (Ctrl+C), run `npm run managed:start` again, and repeat `items:list`: the same items
|
|
121
|
+
and quantities come back. Or run the automated check, which writes a `live-test-…` item, kills
|
|
122
|
+
the app, starts a new process and reads it back:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
FELTKIT_MANAGED_LIVE=1 npm run managed:test
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 6. Deploy
|
|
129
|
+
|
|
130
|
+
The image holds no state and no keys. It is configured entirely by the environment at run
|
|
131
|
+
time, so the same image runs anywhere.
|
|
132
|
+
|
|
133
|
+
**Docker (any host).** Locally, with the settings from step 2:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
docker compose --env-file .env.local up --build
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The compose file passes the container only `FELTDB_URL`, `FELTDB_APPLICATION_ID`,
|
|
140
|
+
`FELTDB_ENVIRONMENT` and `FELTDB_TOKEN`. On any other platform, set the first three as plain
|
|
141
|
+
configuration and `FELTDB_TOKEN` from its secret store. Point health checks at
|
|
142
|
+
`GET /_feltkit/health` on port 8080. The image checks that `feltdb.contract.json` matches the
|
|
143
|
+
source at build time.
|
|
144
|
+
|
|
145
|
+
**Fly.io.**
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
fly apps create <your-app-name> # once
|
|
149
|
+
npm run deploy:fly -- --app <your-app-name>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`scripts/deploy-fly.mjs` stages `FELTDB_TOKEN` as a Fly secret by piping it to
|
|
153
|
+
`fly secrets import --stage` on stdin, so it never appears in a command line or output. It then
|
|
154
|
+
runs `fly deploy` with the three non-secret settings. `fly.toml` holds the port, the health check
|
|
155
|
+
and `FELTDB_ENVIRONMENT`.
|
|
156
|
+
|
|
157
|
+
Then repeat steps 4 and 5 against the deployed URL. Create the session from your machine with
|
|
158
|
+
`npm run managed:session`: sessions live in Managed FeltDB, so the deployed app accepts them.
|
|
159
|
+
|
|
160
|
+
## Changing the data model
|
|
161
|
+
|
|
162
|
+
1. Edit `feltkit/app.ts`.
|
|
163
|
+
2. Run `npm run managed:publish`. This regenerates the contract and publishes it with
|
|
164
|
+
`feltdb publish --contract`. FeltDB classifies the change. A change it classifies as breaking,
|
|
165
|
+
migration-requiring or authorization-sensitive (adding a field is `MIGRATION_REQUIRED`) is
|
|
166
|
+
staged, but not activated until you rerun with `-- --approve-destructive-changes`. A refused or
|
|
167
|
+
unapproved publish leaves the active contract in place.
|
|
168
|
+
3. Commit `feltdb.contract.json` with the code, then redeploy. The app that is already running needs
|
|
169
|
+
no restart. Its next request after the promotion is refused once (`CONFLICT`, `feltdbCode:
|
|
170
|
+
REVISION_NOT_ACTIVE`, nothing written, not resent), and every request after that runs under the
|
|
171
|
+
new contract. A compatible change, such as a new optional field, keeps the old code working. An
|
|
172
|
+
incompatible one refuses the old code's writes until the new code is deployed: FeltDB never
|
|
173
|
+
stores them under the old contract.
|
|
174
|
+
|
|
175
|
+
## Keys: rotate, narrow, revoke
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npx feltdb managed keys list # ids, names, scopes; never secrets
|
|
179
|
+
npx feltdb managed keys create --name inventory-runtime-2 --scopes minimal # new FELTDB_TOKEN in .env.local
|
|
180
|
+
npm run deploy:fly -- --app <your-app-name> # or update your platform's secret
|
|
181
|
+
npx feltdb managed keys revoke inventory-runtime # the old key now gets AUTHENTICATION_REQUIRED
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`--stdout` writes `FELTDB_TOKEN=…` to a pipe (for example into a secret manager) and is refused on a
|
|
185
|
+
terminal. A key is bound to this application. Wildcard (`*`) keys are operator keys and are
|
|
186
|
+
refused.
|
|
187
|
+
|
|
188
|
+
## Troubleshooting
|
|
189
|
+
|
|
190
|
+
| You see | Means | Do |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `Managed FeltDB configuration is incomplete: FELTDB_… is not set` | the launcher found no value in the environment or `.env.local` | run `npm run managed:setup`, or set it on your platform |
|
|
193
|
+
| startup fails with `AUTHENTICATION_REQUIRED` | the runtime key is wrong, revoked, or for another FeltDB | mint a new one (`feltdb managed keys create`) and update `FELTDB_TOKEN` |
|
|
194
|
+
| `FORBIDDEN` | the key is for another application or lacks a scope | check `FELTDB_APPLICATION_ID`; mint with `--scopes minimal` |
|
|
195
|
+
| `NOT_FOUND … application environment has no active contract` | nothing is promoted to `FELTDB_ENVIRONMENT` | `npm run managed:publish`, or fix `FELTDB_ENVIRONMENT` |
|
|
196
|
+
| `UNKNOWN_COLLECTION` or `record contains an unknown field` | the contract is older than the code | `npm run managed:publish` (approve it) |
|
|
197
|
+
| `CONFLICT` with `feltdbCode: REVISION_NOT_ACTIVE` | a contract was promoted since this process's last request; nothing was written, and FeltKit did not resend it | the request was built for the previous contract: if it still makes sense, send it again, and it runs under the new contract (once per promotion). Retrying it is the caller's decision, never automatic |
|
|
198
|
+
| startup fails with `SERVICE_UNAVAILABLE` / names `FELTDB_URL` | FeltDB can't be reached | check `FELTDB_URL` and network egress. Managed FeltDB's `/health` and `/ready` are public |
|
|
199
|
+
| `FELTDB_ENVIRONMENT is "…", but this is FeltKit's … environment` | the two disagree | FeltKit uses the FeltDB environment of its own name: set one of them |
|
|
200
|
+
| `This application enables AppPort Services` from `feltkit contract` | services can't run on Managed FeltDB yet | remove `services`, or use local or self-hosted FeltDB |
|
|
201
|
+
| `The production environment is already served by pid …` | a FeltKit server for this environment is running on this host | stop it first (or run the CLI on another machine) |
|
|
202
|
+
|
|
203
|
+
## Clean up
|
|
204
|
+
|
|
205
|
+
- Revoke keys you no longer use: `npx feltdb managed keys revoke <name-or-id>`.
|
|
206
|
+
- `live-test-…` items written by `managed:test` stay in your application. They are ordinary
|
|
207
|
+
records, so remove them with your own mutation if you need to.
|
|
208
|
+
- Delete the Fly app with `fly apps destroy <your-app-name>`. Local files: `.env.local`,
|
|
209
|
+
`.feltdb/`, `.feltkit/`.
|
|
210
|
+
- The managed application itself is deleted from your FeltDB account. This repository has no
|
|
211
|
+
command that deletes it.
|
|
212
|
+
|
|
213
|
+
## What's verified, and where
|
|
214
|
+
|
|
215
|
+
- `npm test` (here): persistence, refusals, roles, the configuration contract, the Fly deploy
|
|
216
|
+
script with a stand-in `fly`.
|
|
217
|
+
- `npm run test:managed` in the FeltKit repository: this example's full workflow, setup through
|
|
218
|
+
refusals, against a separately running, auth-on `feltdb-server` provisioned the way Managed
|
|
219
|
+
FeltDB is. It covers app and authority restarts, an idempotent retry across a restart, a
|
|
220
|
+
refused key, an unreachable authority, and no secret in any output.
|
|
221
|
+
- `FELTKIT_MANAGED_LIVE=1 npm run managed:test`: the live service. It runs only with your
|
|
222
|
+
credentials.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Run the image locally against Managed FeltDB, with the settings `npm run managed:setup` wrote:
|
|
2
|
+
#
|
|
3
|
+
# docker compose --env-file .env.local up --build
|
|
4
|
+
#
|
|
5
|
+
# Compose reads .env.local only to fill in the variables below; the container receives these
|
|
6
|
+
# four and nothing else (not the control key).
|
|
7
|
+
services:
|
|
8
|
+
inventory:
|
|
9
|
+
build: .
|
|
10
|
+
ports:
|
|
11
|
+
- "8080:8080"
|
|
12
|
+
environment:
|
|
13
|
+
FELTDB_URL: ${FELTDB_URL:?run npm run managed:setup or set FELTDB_URL}
|
|
14
|
+
FELTDB_APPLICATION_ID: ${FELTDB_APPLICATION_ID:?run npm run managed:setup or set FELTDB_APPLICATION_ID}
|
|
15
|
+
FELTDB_ENVIRONMENT: ${FELTDB_ENVIRONMENT:-production}
|
|
16
|
+
FELTDB_TOKEN: ${FELTDB_TOKEN:?run npm run managed:setup or set FELTDB_TOKEN}
|
|
17
|
+
restart: unless-stopped
|