@fabrika/app 0.8.2 → 0.9.0-cloudflare.1
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 +40 -24
- package/skill/SKILL.md +27 -3
- package/skill/references/app-setup.md +8 -8
- package/skill/references/dev.md +2 -1
- package/skill/references/endpoint.md +7 -3
- package/skill/references/errors.md +39 -35
- package/skill/references/files.md +4 -2
- package/skill/references/table-and-migration.md +19 -2
- package/skill/references/tests.md +11 -0
- package/skill/references/webhooks-links-connectors.md +1 -1
- package/src/build-recipe.ts +198 -0
- package/src/build.ts +55 -4
- package/src/bun.ts +23 -8
- package/src/cloudflare/application.ts +352 -0
- package/src/cloudflare/bindings.ts +138 -0
- package/src/cloudflare/names.ts +10 -0
- package/src/cloudflare/ops.ts +246 -0
- package/src/cloudflare/r2-files.ts +172 -0
- package/src/cloudflare.ts +99 -146
- package/src/contract/app-id.ts +5 -0
- package/src/contract/artifact-schema.ts +2 -0
- package/src/contract/build.ts +35 -1
- package/src/contract/compile.ts +1 -0
- package/src/contract/context.ts +9 -0
- package/src/contract/declaration.ts +2 -0
- package/src/contract/define.ts +28 -2
- package/src/contract/kit.ts +4 -1
- package/src/contract/platform.ts +3 -2
- package/src/contract/vocabulary.ts +26 -8
- package/src/dev/changes.ts +35 -0
- package/src/dev/contract-digest.ts +17 -0
- package/src/dev/index.ts +11 -0
- package/src/dev/launcher.ts +713 -0
- package/src/dev/output.ts +72 -0
- package/src/dev/wire.ts +136 -0
- package/src/files/archive.ts +2 -2
- package/src/files/job-names.ts +12 -0
- package/src/files/kinds.ts +19 -7
- package/src/files/scan.ts +3 -4
- package/src/files/streams.ts +3 -1
- package/src/implement/anonymous.ts +11 -1
- package/src/implement/boot.ts +61 -4
- package/src/implement/bounded.ts +21 -2
- package/src/implement/capability.ts +15 -3
- package/src/implement/dispatch.ts +249 -145
- package/src/implement/failures.ts +17 -1
- package/src/implement/faults.ts +1 -0
- package/src/implement/index.ts +1 -0
- package/src/implement/invocation.ts +8 -0
- package/src/implement/principal.ts +25 -2
- package/src/implement/terminals.ts +93 -0
- package/src/implement/workload.ts +24 -13
- package/src/jobs/report.ts +5 -1
- package/src/jobs/runner.ts +33 -17
- package/src/jobs/schedule.ts +7 -1
- package/src/jobs/scrub.ts +14 -1
- package/src/testing/endpoint-access.ts +180 -137
- package/src/testing/kit.ts +20 -2
- package/src/testing/proxy.ts +5 -5
- package/types/src/agent-skill.d.ts +29 -0
- package/types/src/build-recipe.d.ts +70 -0
- package/types/src/build.d.ts +29 -0
- package/types/src/bun.d.ts +66 -0
- package/types/src/client/caller.d.ts +19 -0
- package/types/src/client/contract.d.ts +131 -0
- package/types/src/client/error-reporting.d.ts +66 -0
- package/types/src/client/index.d.ts +4 -0
- package/types/src/client/rpc.d.ts +14 -0
- package/types/src/client/share.d.ts +2 -0
- package/types/src/client/transport.d.ts +42 -0
- package/types/src/cloudflare/application.d.ts +42 -0
- package/types/src/cloudflare/bindings.d.ts +27 -0
- package/types/src/cloudflare/names.d.ts +6 -0
- package/types/src/cloudflare/ops.d.ts +46 -0
- package/types/src/cloudflare/r2-files.d.ts +9 -0
- package/types/src/cloudflare.d.ts +42 -0
- package/types/src/contract/app-id.d.ts +2 -0
- package/types/src/contract/artifact-schema.d.ts +8 -0
- package/types/src/contract/attachment.d.ts +41 -0
- package/types/src/contract/build.d.ts +12 -0
- package/types/src/contract/compile.d.ts +11 -0
- package/types/src/contract/connect.d.ts +51 -0
- package/types/src/contract/context.d.ts +510 -0
- package/types/src/contract/declaration.d.ts +307 -0
- package/types/src/contract/defaults.d.ts +208 -0
- package/types/src/contract/define.d.ts +376 -0
- package/types/src/contract/file.d.ts +44 -0
- package/types/src/contract/group.d.ts +274 -0
- package/types/src/contract/handled.d.ts +18 -0
- package/types/src/contract/index.d.ts +19 -0
- package/types/src/contract/kit.d.ts +211 -0
- package/types/src/contract/platform.d.ts +9 -0
- package/types/src/contract/problems.d.ts +14 -0
- package/types/src/contract/redirect.d.ts +25 -0
- package/types/src/contract/schema-from-shape.d.ts +10 -0
- package/types/src/contract/schema-shape.d.ts +5 -0
- package/types/src/contract/snapshot-source.d.ts +15 -0
- package/types/src/contract/token.d.ts +58 -0
- package/types/src/contract/vocabulary.d.ts +32 -0
- package/types/src/contract/webhook-binding.d.ts +24 -0
- package/types/src/contract/webhook-scheme.d.ts +23 -0
- package/types/src/dev/changes.d.ts +7 -0
- package/types/src/dev/contract-digest.d.ts +1 -0
- package/types/src/dev/index.d.ts +2 -0
- package/types/src/dev/launcher.d.ts +32 -0
- package/types/src/dev/output.d.ts +28 -0
- package/types/src/dev/wire.d.ts +126 -0
- package/types/src/disk-assets.d.ts +3 -0
- package/types/src/errors.d.ts +46 -0
- package/types/src/fetch.d.ts +7 -0
- package/types/src/files/archive.d.ts +26 -0
- package/types/src/files/clamscan.d.ts +32 -0
- package/types/src/files/handle.d.ts +106 -0
- package/types/src/files/job-names.d.ts +9 -0
- package/types/src/files/kinds.d.ts +101 -0
- package/types/src/files/scan.d.ts +78 -0
- package/types/src/files/sniff.d.ts +7 -0
- package/types/src/files/streams.d.ts +17 -0
- package/types/src/files/treatment.d.ts +18 -0
- package/types/src/from-row.d.ts +20 -0
- package/types/src/honeypot.d.ts +2 -0
- package/types/src/implement/anonymous.d.ts +28 -0
- package/types/src/implement/attachment.d.ts +14 -0
- package/types/src/implement/boot.d.ts +114 -0
- package/types/src/implement/bounded.d.ts +32 -0
- package/types/src/implement/browser-config.d.ts +19 -0
- package/types/src/implement/capability.d.ts +142 -0
- package/types/src/implement/challenge.d.ts +75 -0
- package/types/src/implement/client-address.d.ts +11 -0
- package/types/src/implement/connect.d.ts +67 -0
- package/types/src/implement/crashes.d.ts +31 -0
- package/types/src/implement/dispatch.d.ts +220 -0
- package/types/src/implement/egress.d.ts +14 -0
- package/types/src/implement/environment.d.ts +14 -0
- package/types/src/implement/failures.d.ts +39 -0
- package/types/src/implement/faults.d.ts +45 -0
- package/types/src/implement/headers.d.ts +7 -0
- package/types/src/implement/implement.d.ts +58 -0
- package/types/src/implement/index.d.ts +25 -0
- package/types/src/implement/invocation.d.ts +125 -0
- package/types/src/implement/membership.d.ts +201 -0
- package/types/src/implement/multipart.d.ts +36 -0
- package/types/src/implement/params.d.ts +7 -0
- package/types/src/implement/principal.d.ts +57 -0
- package/types/src/implement/project.d.ts +11 -0
- package/types/src/implement/public.d.ts +61 -0
- package/types/src/implement/serve.d.ts +25 -0
- package/types/src/implement/terminals.d.ts +33 -0
- package/types/src/implement/tokens.d.ts +49 -0
- package/types/src/implement/types.d.ts +243 -0
- package/types/src/implement/urls.d.ts +15 -0
- package/types/src/implement/verifiers.d.ts +19 -0
- package/types/src/implement/webhook.d.ts +55 -0
- package/types/src/implement/workload.d.ts +56 -0
- package/types/src/implement/xlsx.d.ts +6 -0
- package/types/src/index.d.ts +12 -0
- package/types/src/jobs/exception.d.ts +40 -0
- package/types/src/jobs/mail.d.ts +67 -0
- package/types/src/jobs/report.d.ts +56 -0
- package/types/src/jobs/runner.d.ts +151 -0
- package/types/src/jobs/schedule.d.ts +17 -0
- package/types/src/jobs/scrub.d.ts +12 -0
- package/types/src/jobs/types.d.ts +98 -0
- package/types/src/react/caller.d.ts +30 -0
- package/types/src/react/errors.d.ts +40 -0
- package/types/src/react/hooks.d.ts +110 -0
- package/types/src/react/index.d.ts +5 -0
- package/types/src/react/provider.d.ts +58 -0
- package/types/src/react/store.d.ts +68 -0
- package/types/src/react/testing.d.ts +28 -0
- package/types/src/react/types.d.ts +33 -0
- package/types/src/rpc/builder.d.ts +52 -0
- package/types/src/rpc/contract.d.ts +23 -0
- package/types/src/rpc/dispatcher.d.ts +11 -0
- package/types/src/rpc/types.d.ts +62 -0
- package/types/src/service/define.d.ts +42 -0
- package/types/src/service/index.d.ts +3 -0
- package/types/src/service/serve.d.ts +28 -0
- package/types/src/standard-schema.d.ts +55 -0
- package/types/src/testing/caller.d.ts +95 -0
- package/types/src/testing/challenge.d.ts +11 -0
- package/types/src/testing/dev.d.ts +97 -0
- package/types/src/testing/endpoint-access.d.ts +107 -0
- package/types/src/testing/index.d.ts +12 -0
- package/types/src/testing/isolation.d.ts +3 -0
- package/types/src/testing/kit.d.ts +187 -0
- package/types/src/testing/membership.d.ts +132 -0
- package/types/src/testing/policy-isolation.d.ts +113 -0
- package/types/src/testing/preload.d.ts +1 -0
- package/types/src/testing/proxy.d.ts +40 -0
- package/types/src/testing/runner.d.ts +8 -0
- package/types/src/testing/sample-file.d.ts +5 -0
- package/types/src/testing/status.d.ts +4 -0
- package/types/src/undumpable.d.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabrika/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-cloudflare.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,77 +14,93 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"src",
|
|
16
16
|
"skill",
|
|
17
|
-
"!src/**/__tests__/**"
|
|
17
|
+
"!src/**/__tests__/**",
|
|
18
|
+
"types"
|
|
18
19
|
],
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
21
22
|
"bun": "./src/index.ts",
|
|
22
|
-
"types": "./src/index.ts",
|
|
23
|
+
"types": "./types/src/index.d.ts",
|
|
23
24
|
"default": "./src/index.ts"
|
|
24
25
|
},
|
|
25
26
|
"./bun": {
|
|
26
27
|
"bun": "./src/bun.ts",
|
|
27
|
-
"types": "./src/bun.ts",
|
|
28
|
+
"types": "./types/src/bun.d.ts",
|
|
28
29
|
"default": "./src/bun.ts"
|
|
29
30
|
},
|
|
30
31
|
"./cloudflare": {
|
|
31
32
|
"bun": "./src/cloudflare.ts",
|
|
32
|
-
"types": "./src/cloudflare.ts",
|
|
33
|
+
"types": "./types/src/cloudflare.d.ts",
|
|
33
34
|
"default": "./src/cloudflare.ts"
|
|
34
35
|
},
|
|
35
36
|
"./contract": {
|
|
36
37
|
"bun": "./src/contract/index.ts",
|
|
37
|
-
"types": "./src/contract/index.ts",
|
|
38
|
+
"types": "./types/src/contract/index.d.ts",
|
|
38
39
|
"default": "./src/contract/index.ts"
|
|
39
40
|
},
|
|
40
41
|
"./client": {
|
|
41
42
|
"bun": "./src/client/index.ts",
|
|
42
|
-
"types": "./src/client/index.ts",
|
|
43
|
+
"types": "./types/src/client/index.d.ts",
|
|
43
44
|
"default": "./src/client/index.ts"
|
|
44
45
|
},
|
|
45
46
|
"./react": {
|
|
46
47
|
"bun": "./src/react/index.ts",
|
|
47
|
-
"types": "./src/react/index.ts",
|
|
48
|
+
"types": "./types/src/react/index.d.ts",
|
|
48
49
|
"default": "./src/react/index.ts"
|
|
49
50
|
},
|
|
50
51
|
"./react/testing": {
|
|
51
52
|
"bun": "./src/react/testing.ts",
|
|
52
|
-
"types": "./src/react/testing.ts",
|
|
53
|
+
"types": "./types/src/react/testing.d.ts",
|
|
53
54
|
"default": "./src/react/testing.ts"
|
|
54
55
|
},
|
|
55
56
|
"./implement": {
|
|
56
57
|
"bun": "./src/implement/index.ts",
|
|
57
|
-
"types": "./src/implement/index.ts",
|
|
58
|
+
"types": "./types/src/implement/index.d.ts",
|
|
58
59
|
"default": "./src/implement/index.ts"
|
|
59
60
|
},
|
|
60
61
|
"./service": {
|
|
61
62
|
"bun": "./src/service/index.ts",
|
|
62
|
-
"types": "./src/service/index.ts",
|
|
63
|
+
"types": "./types/src/service/index.d.ts",
|
|
63
64
|
"default": "./src/service/index.ts"
|
|
64
65
|
},
|
|
65
66
|
"./build": {
|
|
66
67
|
"bun": "./src/build.ts",
|
|
67
|
-
"types": "./src/build.ts",
|
|
68
|
+
"types": "./types/src/build.d.ts",
|
|
68
69
|
"default": "./src/build.ts"
|
|
69
70
|
},
|
|
70
71
|
"./testing": {
|
|
71
72
|
"bun": "./src/testing/index.ts",
|
|
72
|
-
"types": "./src/testing/index.ts",
|
|
73
|
+
"types": "./types/src/testing/index.d.ts",
|
|
73
74
|
"default": "./src/testing/index.ts"
|
|
74
75
|
},
|
|
75
76
|
"./testing/isolation": {
|
|
76
77
|
"bun": "./src/testing/isolation.ts",
|
|
77
|
-
"types": "./src/testing/isolation.ts",
|
|
78
|
+
"types": "./types/src/testing/isolation.d.ts",
|
|
78
79
|
"default": "./src/testing/isolation.ts"
|
|
79
80
|
},
|
|
80
81
|
"./testing/preload": {
|
|
81
82
|
"bun": "./src/testing/preload.ts",
|
|
82
|
-
"types": "./src/testing/preload.ts",
|
|
83
|
+
"types": "./types/src/testing/preload.d.ts",
|
|
83
84
|
"default": "./src/testing/preload.ts"
|
|
84
85
|
},
|
|
86
|
+
"./dev": {
|
|
87
|
+
"bun": "./src/dev/index.ts",
|
|
88
|
+
"types": "./types/src/dev/index.d.ts",
|
|
89
|
+
"default": "./src/dev/index.ts"
|
|
90
|
+
},
|
|
91
|
+
"./dev/wire": {
|
|
92
|
+
"bun": "./src/dev/wire.ts",
|
|
93
|
+
"types": "./types/src/dev/wire.d.ts",
|
|
94
|
+
"default": "./src/dev/wire.ts"
|
|
95
|
+
},
|
|
96
|
+
"./dev/changes": {
|
|
97
|
+
"bun": "./src/dev/changes.ts",
|
|
98
|
+
"types": "./types/src/dev/changes.d.ts",
|
|
99
|
+
"default": "./src/dev/changes.ts"
|
|
100
|
+
},
|
|
85
101
|
"./agent-skill": {
|
|
86
102
|
"bun": "./src/agent-skill.ts",
|
|
87
|
-
"types": "./src/agent-skill.ts",
|
|
103
|
+
"types": "./types/src/agent-skill.d.ts",
|
|
88
104
|
"default": "./src/agent-skill.ts"
|
|
89
105
|
}
|
|
90
106
|
},
|
|
@@ -93,14 +109,14 @@
|
|
|
93
109
|
"test": "bun test --path-ignore-patterns='src/react/**' && bun test --isolate src/react"
|
|
94
110
|
},
|
|
95
111
|
"dependencies": {
|
|
96
|
-
"@fabrika/auth": "0.
|
|
97
|
-
"@fabrika/auth-core": "0.
|
|
98
|
-
"@fabrika/db": "0.
|
|
99
|
-
"@fabrika/email": "0.
|
|
100
|
-
"@fabrika/jwks": "0.
|
|
101
|
-
"@fabrika/platform": "0.
|
|
102
|
-
"@fabrika/platform-node": "0.
|
|
103
|
-
"@fabrika/proxy-core": "0.
|
|
112
|
+
"@fabrika/auth": "0.9.0-cloudflare.1",
|
|
113
|
+
"@fabrika/auth-core": "0.9.0-cloudflare.1",
|
|
114
|
+
"@fabrika/db": "0.9.0-cloudflare.1",
|
|
115
|
+
"@fabrika/email": "0.9.0-cloudflare.1",
|
|
116
|
+
"@fabrika/jwks": "0.9.0-cloudflare.1",
|
|
117
|
+
"@fabrika/platform": "0.9.0-cloudflare.1",
|
|
118
|
+
"@fabrika/platform-node": "0.9.0-cloudflare.1",
|
|
119
|
+
"@fabrika/proxy-core": "0.9.0-cloudflare.1",
|
|
104
120
|
"altcha-lib": "2.6.0",
|
|
105
121
|
"jose": "^5.9.0"
|
|
106
122
|
},
|
package/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fabrika-app
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.9.0-cloudflare.1
|
|
4
4
|
description: >
|
|
5
5
|
How to write and change a Fabrika application: src/vocabulary.ts, db/migrations/, src/db/tables.ts, the
|
|
6
6
|
slices' endpoints.ts with their handlers, fabrika.contract.ts, jobs, files, webhooks, share links, connectors
|
|
@@ -258,7 +258,7 @@ Which promise the types, the checker, boot, the runtime or the approver keeps, a
|
|
|
258
258
|
- No `as` (except `as const`), `any`, `@ts-ignore`, `@ts-expect-error`, `process.env` or `Bun.env`.
|
|
259
259
|
- Never log an error object, a request, headers or the environment.
|
|
260
260
|
- Never edit a generated file (`src/db/schema.generated.ts`, the generated tests, `fabrika/entry.generated.ts`,
|
|
261
|
-
`fabrika/connect/`) or a committed migration. Regenerate the file; write a new migration.
|
|
261
|
+
`fabrika/migrations.generated.ts`, `fabrika/connect/`) or a committed migration. Regenerate the file; write a new migration.
|
|
262
262
|
|
|
263
263
|
## Ask the human first
|
|
264
264
|
|
|
@@ -295,6 +295,29 @@ bunx tsc --noEmit # once, at the end
|
|
|
295
295
|
bunx fabrika-check . # once, after tsc; refusals fail; hints print below them
|
|
296
296
|
```
|
|
297
297
|
|
|
298
|
+
**On Cloudflare** (`fabrika.config.ts` imports `defineApp` from `@fabrika/provider-cloudflare`) the application runs in
|
|
299
|
+
one Durable Object over SQLite. Its `db/migrations/*.sql` are SQLite (`references/table-and-migration.md`), and the
|
|
300
|
+
object applies them when it boots, so there is no `migrate` and no command needs a database URL or `FABRIKA_APP_ID`.
|
|
301
|
+
The tests run on `@fabrika/db/testing-sqlite` (`references/tests.md`), in memory, and never skip. A handler holds the
|
|
302
|
+
object's transaction, so its `limits.timeoutMs` is at most 25 s. No `sql` (`ctx.sql`) and no `scan: true`: the
|
|
303
|
+
checker, the deploy and the boot refuse both.
|
|
304
|
+
|
|
305
|
+
The object runs one transaction at a time, so every `await` in a handler that may write holds the whole environment:
|
|
306
|
+
IAM, R2, a `fetch`, a `ctx.connect` call, a binding. One 2 s call caps the environment at half a request a second. A
|
|
307
|
+
query holds no transaction: each of its statements runs on its own, so two of its reads may see different committed
|
|
308
|
+
states. Inside a
|
|
309
|
+
mutation keep I/O to the framework's own: IAM, and the file it writes. A slow outside call goes into a row, the
|
|
310
|
+
mutation calls `ctx.kick('<job>')`, and the job makes the call outside `ctx.transaction` (`references/jobs.md`). The
|
|
311
|
+
checker hints on a mutation that declares `egress` or `connect` (`cloudflare-held-call`), and refuses
|
|
312
|
+
`DEFAULT CURRENT_TIMESTAMP` in a migration (`cloudflare-current-timestamp`).
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
bunx fabrika db new <slug> # a new SQLite migration file
|
|
316
|
+
bunx fabrika db codegen # applies the migrations in memory; writes src/db/schema.generated.ts
|
|
317
|
+
bunx fabrika db test-scaffold # the generated tests, on @fabrika/db/testing-sqlite
|
|
318
|
+
bun test --isolate # no database URL; then tsc and fabrika-check as above
|
|
319
|
+
```
|
|
320
|
+
|
|
298
321
|
A generated file changes only through its command (`codegen`, `test-scaffold`); running it is the loop, not an edit.
|
|
299
322
|
Format and lint only the files you wrote, with the repository's own tools: in the fabrika-platform repository
|
|
300
323
|
`bunx dprint fmt <files>` and `bunx biome lint <files>`, never `biome check` (its formatter fights dprint), and never a
|
|
@@ -326,7 +349,8 @@ e-mails them, a link holder's files, HR's review with its history.
|
|
|
326
349
|
|
|
327
350
|
## Not built yet
|
|
328
351
|
|
|
329
|
-
-
|
|
352
|
+
- On Cloudflare, where the application runs in one Durable Object over its SQLite and its `db/migrations/*.sql` are
|
|
353
|
+
SQLite, `sql` (`ctx.sql`) and a file kind with `scan: true` are refused. Everything else on the road runs there.
|
|
330
354
|
- Jobs are cron only and app-wide: no `ctx.jobs.enqueue`, no job per scope. A `ctx.kick` starts a run of its job; it
|
|
331
355
|
carries nothing, and the job finds its work in a queue table with `claim`. A list write is off the road (`sql`).
|
|
332
356
|
- A read across tables (a join, a filter over a related table, an aggregate over several) is off the road (`sql`).
|
|
@@ -19,20 +19,20 @@ the SPA is added with it.
|
|
|
19
19
|
"test": "bun test --isolate"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@fabrika/app": "0.
|
|
23
|
-
"@fabrika/db": "0.
|
|
24
|
-
"@fabrika/provider-zerops": "0.
|
|
25
|
-
"@fabrika/proxy-core": "0.
|
|
26
|
-
"@fabrika/ui": "0.
|
|
22
|
+
"@fabrika/app": "0.9.0-cloudflare.1",
|
|
23
|
+
"@fabrika/db": "0.9.0-cloudflare.1",
|
|
24
|
+
"@fabrika/provider-zerops": "0.9.0-cloudflare.1",
|
|
25
|
+
"@fabrika/proxy-core": "0.9.0-cloudflare.1",
|
|
26
|
+
"@fabrika/ui": "0.9.0-cloudflare.1",
|
|
27
27
|
"kysely": "0.29.6",
|
|
28
28
|
"react": "^19.0.0",
|
|
29
29
|
"react-dom": "^19.0.0",
|
|
30
30
|
"zod": "^3.25.76"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@fabrika/check": "0.
|
|
34
|
-
"@fabrika/cli": "0.
|
|
35
|
-
"@fabrika/platform": "0.
|
|
33
|
+
"@fabrika/check": "0.9.0-cloudflare.1",
|
|
34
|
+
"@fabrika/cli": "0.9.0-cloudflare.1",
|
|
35
|
+
"@fabrika/platform": "0.9.0-cloudflare.1",
|
|
36
36
|
"@happy-dom/global-registrator": "^20.11.2",
|
|
37
37
|
"@types/bun": "^1.4.3",
|
|
38
38
|
"@types/react": "^19.0.0",
|
package/skill/references/dev.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# references/dev.md — previewing the application with `fabrika app dev`
|
|
2
2
|
|
|
3
3
|
`bunx fabrika app dev` serves the application on `http://127.0.0.1:3000/` through its test kit: the proxy decision,
|
|
4
|
-
the stand-in IAM, a throwaway schema of `FABRIKA_TEST_POSTGRES_URL
|
|
4
|
+
the stand-in IAM, a throwaway schema of `FABRIKA_TEST_POSTGRES_URL` (on Cloudflare an in-memory SQLite database,
|
|
5
|
+
without a URL), and each SPA from its build (run
|
|
5
6
|
`bun run build:web` first). Use it to look at the screens, never in place of a test.
|
|
6
7
|
|
|
7
8
|
## The seed
|
|
@@ -421,7 +421,7 @@ export const workloadRows = (
|
|
|
421
421
|
## Raw SQL: `sql: 'read'`
|
|
422
422
|
|
|
423
423
|
Raw SQL is legitimate for a join, a report, an `OR` or `IS NULL` filter, or a bulk write. Declare it on the endpoint;
|
|
424
|
-
`ctx.sql` exists only there.
|
|
424
|
+
`ctx.sql` exists only there. It runs on Postgres only: on Cloudflare a site that declares `sql` is refused.
|
|
425
425
|
|
|
426
426
|
```ts src/reports/endpoints.ts
|
|
427
427
|
import { attachment } from '@fabrika/app/contract'
|
|
@@ -564,7 +564,7 @@ Every procedure takes `admission` (who may send it, how often) and `limits` (how
|
|
|
564
564
|
| `admission.origins` | `'same-origin'` (default) or `'listed'` | `'same-origin'`, `'listed'` or `'any'` |
|
|
565
565
|
| `admission.challenge` | none | `{ provider: 'altcha', secret, action?, cost?, maxAgeMs? }`, or `{ provider: 'turnstile', secret, siteKey, action? }` |
|
|
566
566
|
| `limits.maxBytes` | 64 KiB, at most 1 MiB; with file fields 64 KiB plus each file at its kind's `maxBytes`, at most 48 MiB | the same |
|
|
567
|
-
| `limits.timeoutMs` | 10 s, at most 60 s
|
|
567
|
+
| `limits.timeoutMs` | 10 s, at most 60 s; at most 25 s on Cloudflare | the same |
|
|
568
568
|
|
|
569
569
|
- A declared `rate` merges into the default; `null` turns one bucket off. Past a bucket the answer is 429.
|
|
570
570
|
- Every bucket is charged before the body is read and stays charged whatever the handler does. A bucket is a database
|
|
@@ -642,7 +642,7 @@ export const app = defineApp(vocabulary, tables, {
|
|
|
642
642
|
- A public procedure updates or removes a row only by an id a verified token of its request carries, with
|
|
643
643
|
`expectVersion`.
|
|
644
644
|
- Single use and revocation are your state (a row the token names). The keys are the platform's: Control generates
|
|
645
|
-
and rotates them, and an old key verifies until its last token expires.
|
|
645
|
+
and rotates them, and an old key verifies until its last token expires.
|
|
646
646
|
|
|
647
647
|
## Outside calls: `egress`
|
|
648
648
|
|
|
@@ -652,6 +652,10 @@ refused while the site runs (`capability_refused: undeclared_egress`). A public,
|
|
|
652
652
|
none. The checker's `cross-app-fetch` accepts an absolute URL only under an origin some site declares. The approver
|
|
653
653
|
sees every origin. The guard stops mistakes; it is no sandbox.
|
|
654
654
|
|
|
655
|
+
On Cloudflare a procedure waits for its outside call inside the object's transaction, so the whole environment waits
|
|
656
|
+
with it (`SKILL.md`). A mutation records a row and kicks a job, which declares the `egress` and calls outside
|
|
657
|
+
`ctx.transaction` (`references/jobs.md`).
|
|
658
|
+
|
|
655
659
|
## Links to your own procedures: `ctx.url`
|
|
656
660
|
|
|
657
661
|
`ctx.url('<endpoint id>', { input?, query? })` builds an absolute URL of one of the application's own procedures, on
|
|
@@ -8,39 +8,43 @@ architecture, and a refusal's allowed form is the one fix that passes. There is
|
|
|
8
8
|
|
|
9
9
|
The refusals agents meet most, and their fix:
|
|
10
10
|
|
|
11
|
-
| Rule
|
|
12
|
-
|
|
|
13
|
-
| `literal`
|
|
14
|
-
| `contract`
|
|
15
|
-
| `package-replacement`
|
|
16
|
-
| `generated-test-hash`
|
|
17
|
-
| `endpoint-fixture`
|
|
18
|
-
| `raw-sql`
|
|
19
|
-
| `forbidden-import`
|
|
20
|
-
| `forbidden-global`
|
|
21
|
-
| `env-read`
|
|
22
|
-
| `client-identity`
|
|
23
|
-
| `type-assertion`
|
|
24
|
-
| `sensitive-log`
|
|
25
|
-
| `migration-name`
|
|
26
|
-
| `test-only-import`
|
|
27
|
-
| `spa-server-import`
|
|
28
|
-
| `shared-import`
|
|
29
|
-
| `skipped-test`
|
|
30
|
-
| `file-column-migration`
|
|
31
|
-
| `on-conflict-index`
|
|
32
|
-
| `cross-app-fetch`
|
|
33
|
-
| `url-endpoint`
|
|
11
|
+
| Rule | Means | Fix |
|
|
12
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
13
|
+
| `literal` | the shell (contract, vocabulary, tables, groups, schemas) holds a computed value or call | literal objects, `z.*` calls and imported literal consts; move logic into `.handle(…)` |
|
|
14
|
+
| `contract` | the contract does not compile (a reserved role, an unknown action, a wrong field) | the message names the field |
|
|
15
|
+
| `package-replacement` | `package.json` names the app under `@fabrika/*`, or a dependency is `workspace:*` | another name; a registry range such as `^0.0.4` |
|
|
16
|
+
| `generated-test-hash` | a generated test is missing, edited or stale | `bunx fabrika db test-scaffold`, and commit what it writes |
|
|
17
|
+
| `endpoint-fixture` | an endpoint's input cannot be sampled, a fixture or seed names no endpoint, or a member, invitation or link field no endpoint that reads, cancels or revokes one | a literal in `src/__tests__/endpoint-access.fixtures.ts` (`references/tests.md`) |
|
|
18
|
+
| `raw-sql` | a driver's `sql` (imported from `bun`, Kysely, `postgres`) | the handles, or `sql: 'read'` and `ctx.sql` |
|
|
19
|
+
| `forbidden-import` | a driver, an S3 client, `jose`, `node:http`, `child_process` | the handles, a file column with `ctx.files`, a connector |
|
|
20
|
+
| `forbidden-global` | `Bun.sql`, `Bun.serve`, `Bun.spawn`; `globalThis`, `window`, `self`, `global` or `Bun` in a server module | the handles; state lives in a table, not in a global; `crypto.randomUUID()` for an id |
|
|
21
|
+
| `env-read` | `process.env`, `Bun.env` | `defineDeps(build)`, which receives the application's variables |
|
|
22
|
+
| `client-identity` | an input field `userId`, `ownerId`, `createdBy`, `tenantId`, `workspaceId` | the access form's `from`, `ctx.principal.id`, or `createdBy` in the policy |
|
|
23
|
+
| `type-assertion` | `as T` (other than `as const`), `<T>x` | fix the type; `fromRow` parses a row into a schema's type |
|
|
24
|
+
| `sensitive-log` | a log of an error object, a request, headers or the environment | log a short fixed message |
|
|
25
|
+
| `migration-name` | a migration not named `YYYYMMDDHHMMSS_slug.sql` (UTC) | `bunx fabrika db new <slug>` |
|
|
26
|
+
| `test-only-import` | `@fabrika/app/testing`, `@fabrika/app/react/testing`, `@fabrika/db/testing` or `@fabrika/db/testing-sqlite` outside a test file | import them only from `*.test.ts` or `__tests__/` |
|
|
27
|
+
| `spa-server-import` | the SPA loads a server module | the SPA imports `@fabrika/app/client`, the contract, `src/schemas.ts` and `src/shared/` |
|
|
28
|
+
| `shared-import` | a module under `src/shared/` loads a slice, `src/db/`, `@fabrika/*` or a builtin | a shared module imports `zod/v4` and other shared modules only |
|
|
29
|
+
| `skipped-test` | `.skip`, `.only`, `.todo` | run the test |
|
|
30
|
+
| `file-column-migration` | a file column without `REFERENCES fabrika_files (id)` or without an index it leads | both, in a new migration (`references/table-and-migration.md`) |
|
|
31
|
+
| `on-conflict-index` | `insert(…, { onConflict: { columns } })` whose columns are no unique index the migrations create | a `UNIQUE` constraint or a `CREATE UNIQUE INDEX` without `WHERE` on exactly those columns |
|
|
32
|
+
| `cross-app-fetch` | a `fetch` of an absolute URL under no origin a site declares in `egress` | `egress: ['https://…']` on the site that calls it (`references/endpoint.md`) |
|
|
33
|
+
| `url-endpoint` | `ctx.url('<id>')` names no procedure of the contract | the endpoint id `<mount>.<group>.<procedure>` |
|
|
34
|
+
| `cloudflare-sql` | on Cloudflare, a site that declares `sql` | the handles (`ctx.db`), without `sql` |
|
|
35
|
+
| `cloudflare-scan` | on Cloudflare, a file kind with `scan: true` | the kind without `scan`, and no public procedure that takes its files |
|
|
36
|
+
| `cloudflare-current-timestamp` | on Cloudflare, `DEFAULT CURRENT_TIMESTAMP` in a migration: SQLite stores `YYYY-MM-DD HH:MM:SS`, which every read of the table refuses | `DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`, in a new migration |
|
|
34
37
|
|
|
35
38
|
The hints:
|
|
36
39
|
|
|
37
|
-
| Hint | Means
|
|
38
|
-
| ---------------------- |
|
|
39
|
-
| `handler-location` | a `.handle(…)` outside a slice's `endpoints.ts`
|
|
40
|
-
| `data-access-location` | `ctx.db` or `ctx.sql` outside `endpoints.ts` and `queries.ts`
|
|
41
|
-
| `logic-data-access` | `logic.ts` reaches data
|
|
42
|
-
| `cross-slice-queries` | a slice loads another slice's `queries.ts`
|
|
43
|
-
| `sql-undeclared` | `ctx.sql` in a handler whose endpoint declares no `sql`
|
|
40
|
+
| Hint | Means | Fix |
|
|
41
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
42
|
+
| `handler-location` | a `.handle(…)` outside a slice's `endpoints.ts` | move the endpoint into `src/<slice>/endpoints.ts` |
|
|
43
|
+
| `data-access-location` | `ctx.db` or `ctx.sql` outside `endpoints.ts` and `queries.ts` | pass the handle into a function of the slice's `queries.ts` |
|
|
44
|
+
| `logic-data-access` | `logic.ts` reaches data | keep `logic.ts` pure; pass it plain values |
|
|
45
|
+
| `cross-slice-queries` | a slice loads another slice's `queries.ts` | move the function, or call that slice's endpoint |
|
|
46
|
+
| `sql-undeclared` | `ctx.sql` in a handler whose endpoint declares no `sql` | add `sql: 'read'` (or `'write'`) to that endpoint |
|
|
47
|
+
| `cloudflare-held-call` | on Cloudflare, a mutation that declares `egress` or `connect` holds the whole environment while it waits | record a row and `ctx.kick('<job>')`; the job makes the call outside `ctx.transaction` |
|
|
44
48
|
|
|
45
49
|
The first run may report only the contract's refusals: the generated-test findings appear once the contract compiles.
|
|
46
50
|
|
|
@@ -99,11 +103,11 @@ A 500 answers a fixed message; the server log has one line, `fabrika: <endpoint>
|
|
|
99
103
|
fixed failure code and, for a framework refusal, its reason and the reason's fixed text, never a value or your error's
|
|
100
104
|
message. The codes you meet most:
|
|
101
105
|
|
|
102
|
-
| Code | Means
|
|
103
|
-
| -------------------- |
|
|
104
|
-
| `query_refused` | the data layer refused a statement or an operation: `undeclared_reach`, `sql_read_only`, `unknown_column`, `read_only`, `sql_outside_transaction` …; the reason's text names the rule |
|
|
105
|
-
| `capability_refused` | a capability of another kind, dimension or action than the call takes (`wrong_capability`, `wrong_action`, `out_of_reach`), or one kept past its request (`ended`, `foreign_work`)
|
|
106
|
-
| `handler_failed` | your handler threw something else; the log has no more: the Operations issue has the stack, or test the handler
|
|
106
|
+
| Code | Means |
|
|
107
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `query_refused` | the data layer refused a statement or an operation: `undeclared_reach`, `sql_read_only`, `unknown_column`, `read_only`, `sql_outside_transaction`, `sql_dialect` (`ctx.sql` on SQLite, Cloudflare) …; the reason's text names the rule |
|
|
109
|
+
| `capability_refused` | a capability of another kind, dimension or action than the call takes (`wrong_capability`, `wrong_action`, `out_of_reach`), or one kept past its request (`ended`, `foreign_work`) |
|
|
110
|
+
| `handler_failed` | your handler threw something else; the log has no more: the Operations issue has the stack, or test the handler |
|
|
107
111
|
|
|
108
112
|
With an Operations DSN the same 500 is a `RequestFailed` issue in Operations. Its event also carries what your handler
|
|
109
113
|
threw: the error's type, its message, its stack and its `cause` chain. Read the stack there, not in the log. A crash
|
|
@@ -363,7 +363,8 @@ export const erase = app.cron('30 2 * * *', {
|
|
|
363
363
|
## Malware scan
|
|
364
364
|
|
|
365
365
|
`scan: true` on a kind holds every file of it `quarantined` until ClamAV answers clean; on Zerops the platform
|
|
366
|
-
installs ClamAV and runs the scan job.
|
|
366
|
+
installs ClamAV and runs the scan job. No scanner runs on Cloudflare: the deploy and the boot refuse a kind with
|
|
367
|
+
`scan: true` there, and so a public procedure with a file field. A file answer is 409 `file_not_ready` until then and 409 `file_rejected` once
|
|
367
368
|
the file is `rejected`; a list shows each file's `status`, so show it rather than a broken link. In a test, the scan runs with
|
|
368
369
|
`kit.runJob(FILE_SCAN_JOB)` and a `scanner` passed to `testKit` (`references/tests.md`); the generated tests pass their
|
|
369
370
|
own seeded files.
|
|
@@ -401,7 +402,8 @@ the transaction that deletes the row. The file is never copied; the sweep delete
|
|
|
401
402
|
## Storage
|
|
402
403
|
|
|
403
404
|
On Zerops, declare one `object-storage` service in `fabrika.config.ts`; the platform writes `FABRIKA_APP_STORAGE_*`
|
|
404
|
-
from it. Never read those variables, build a storage URL or import an S3 client.
|
|
405
|
+
from it. Never read those variables, build a storage URL or import an S3 client. On Cloudflare the platform binds the
|
|
406
|
+
environment's R2 bucket itself; declare nothing.
|
|
405
407
|
|
|
406
408
|
```text
|
|
407
409
|
// in the services of fabrika.config.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# references/table-and-migration.md — the vocabulary, migrations and table policies
|
|
2
2
|
|
|
3
|
-
The order is fixed: the vocabulary, then the migration, then `fabrika db migrate` and `fabrika db codegen
|
|
4
|
-
table policies, which take the generated `columns`.
|
|
3
|
+
The order is fixed: the vocabulary, then the migration, then `fabrika db migrate` and `fabrika db codegen` (on
|
|
4
|
+
Cloudflare `codegen` only), then the table policies, which take the generated `columns`.
|
|
5
5
|
|
|
6
6
|
## The vocabulary
|
|
7
7
|
|
|
@@ -155,6 +155,23 @@ INSERT INTO priorities (code, label, rank) VALUES ('low', 'Low', 1), ('normal',
|
|
|
155
155
|
- Never edit, rename or delete a committed migration; add a new one. Change a schema by expand, switch, contract, and
|
|
156
156
|
ask before you drop or rename anything.
|
|
157
157
|
|
|
158
|
+
## On Cloudflare: SQLite migrations
|
|
159
|
+
|
|
160
|
+
An application on Cloudflare (its `fabrika.config.ts` imports `defineApp` from `@fabrika/provider-cloudflare`) writes
|
|
161
|
+
its migrations in SQLite. Its Durable Object applies them when it boots, each file once in name order, in a transaction
|
|
162
|
+
of its own, so there is no `fabrika db migrate`; `fabrika db codegen` applies them to an in-memory database and needs
|
|
163
|
+
no URL. The rules above for keys, chains, composite foreign keys, `version` and `created_by` hold unchanged, and the
|
|
164
|
+
database enforces foreign keys.
|
|
165
|
+
|
|
166
|
+
- The declared types codegen reads: `TEXT` (a UUID too), `INTEGER`, `REAL`, `BLOB`; `BOOLEAN`, stored as 0 or 1 and
|
|
167
|
+
read as a boolean; `TIMESTAMP`, stored as UTC ISO text and read as a `Date`; `DATE`, `YYYY-MM-DD` text. Any other
|
|
168
|
+
declared type is refused. A `STRICT` table admits only `INTEGER`, `REAL`, `TEXT` and `BLOB`, so a table with a
|
|
169
|
+
`BOOLEAN`, `TIMESTAMP` or `DATE` column is not `STRICT`.
|
|
170
|
+
- SQLite has no `ENUM`: a column with a fixed set of values is `TEXT` with `CHECK (<column> IN ('a', 'b'))`. The
|
|
171
|
+
generated tests write its first value.
|
|
172
|
+
- No transaction control (`BEGIN`, `COMMIT`, `SAVEPOINT`) and no Postgres syntax (`timestamptz`, `::` casts,
|
|
173
|
+
`GENERATED ALWAYS AS IDENTITY`, `CREATE TYPE`).
|
|
174
|
+
|
|
158
175
|
## Database code is a declared deviation
|
|
159
176
|
|
|
160
177
|
A function, trigger, view, rule, policy or `DO` block in a migration is allowed. The migration file declares it, and
|
|
@@ -59,6 +59,14 @@ export const members = { 'api.tasks.assign': ['assignee'] }
|
|
|
59
59
|
table of the table that owns it, or a table it contains; the checker refuses a row further down
|
|
60
60
|
(`endpoint-fixture`). Without it they send a sample, the handler
|
|
61
61
|
finds no row (404), and the case fails.
|
|
62
|
+
- `appRows['<endpoint id>']` gives the rows of application tables (`scope: { app }`) that the endpoint's handler reads
|
|
63
|
+
for the caller's own scope, by table, with the columns they need: `export const appRows = { 'portal.guide': { guides:
|
|
64
|
+
{ active: true } } }`. Each of its leak and own-object cases seeds one row of each before it asks and removes them
|
|
65
|
+
once it is over, with every row that refers to them (a download the handler logged), so a handler that reads the
|
|
66
|
+
newest guide answers 200 instead of 404, and no other endpoint's case finds the row. A foreign key from one given row
|
|
67
|
+
to another holds that row's key. A text column may be given; the key, the version, the creator and a file column may
|
|
68
|
+
not (a file column holds a live, scanned file). Only application tables, and only for an endpoint with a leak or an
|
|
69
|
+
own-object case: the checker refuses a table in a scope, which `rows` and `seeds` address (`endpoint-fixture`).
|
|
62
70
|
- The endpoint id is `<mount>.<group>.<endpoint>` (`api.tasks.close`).
|
|
63
71
|
- The seed of the tests fills every text column with a marker and every other required column with a sample of its
|
|
64
72
|
type: an enum's first label, `1`, `false`, today. A column with a `DEFAULT` keeps it (`status` is `open`). A column
|
|
@@ -196,6 +204,9 @@ if (postgres !== null) {
|
|
|
196
204
|
`<label> skipped: set FABRIKA_TEST_POSTGRES_URL to run the Postgres-backed tests`, and the file registers no test.
|
|
197
205
|
A skip proves nothing. To confirm a file ran, run it alone (`bun test --isolate src/tasks`) and check that the pass
|
|
198
206
|
count is its number of tests, or set `FABRIKA_TEST_POSTGRES_REQUIRED=1`: a missing URL then fails the file.
|
|
207
|
+
- On Cloudflare import `testDatabase` from `@fabrika/db/testing-sqlite` instead, with the same options: it migrates a
|
|
208
|
+
fresh in-memory SQLite database as the Durable Object does, needs no URL and never answers `null`. The generated tests
|
|
209
|
+
already use it.
|
|
199
210
|
- `statusOf(call)` answers 200 when the call resolves and the status of the application's refusal otherwise; any other
|
|
200
211
|
failure (no answer, a bug in the test) is rethrown. Import it beside `testKit`; do not write your own.
|
|
201
212
|
- `kit.testCaller(grants, { kind })` makes a caller: a grant is a role or an action, in one scope or global. A
|
|
@@ -89,7 +89,7 @@ export const integrations = app.group({ callers: ['human'] }, (r) => ({
|
|
|
89
89
|
that binds it takes an `app` capability: `access: { action, app: true }`, `binds`, and
|
|
90
90
|
`ctx.webhooks('<webhook>').bind(ctx.app, account)`.
|
|
91
91
|
- `limits: { maxBytes?, timeoutMs? }`: the body is 1 MiB by default and at most 48 MiB; the handler runs 10 s by
|
|
92
|
-
default, at most 60 s. A webhook may declare `kicks`, `signs`, `egress`, `sql` and `unscoped`, and `ctx.files.put`
|
|
92
|
+
default, at most 60 s (25 s on Cloudflare). A webhook may declare `kicks`, `signs`, `egress`, `sql` and `unscoped`, and `ctx.files.put`
|
|
93
93
|
stores a file a provider sends (an inbound e-mail's attachment) for a file column: raise `limits.maxBytes` to the
|
|
94
94
|
mail you accept.
|
|
95
95
|
- Not covered: a provider that validates its URL with a `GET`, or that needs a custom answer body.
|