create-feltkit 0.3.0 → 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.
@@ -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,7 @@
1
+ node_modules/
2
+ dist/
3
+ .feltkit/
4
+ # Written by `feltdb init-managed` / `feltdb managed keys`: keys and local deployment state. Never commit.
5
+ .env
6
+ .env.*
7
+ .feltdb/
@@ -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