@dokebi/cli 0.4.1 → 0.4.26

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.
Files changed (188) hide show
  1. package/AI.md +262 -57
  2. package/README.md +35 -10
  3. package/catalog.json +2519 -257
  4. package/describe.schema.json +748 -0
  5. package/dist/api-app.d.ts +2 -0
  6. package/dist/api-app.js +35 -0
  7. package/dist/app-urls.js +3 -2
  8. package/dist/bin.js +527 -35
  9. package/dist/capability-guide.d.ts +30 -0
  10. package/dist/capability-guide.js +122 -0
  11. package/dist/catalog.d.ts +16 -2
  12. package/dist/catalog.js +19 -7
  13. package/dist/check/asset-rules.d.ts +6 -0
  14. package/dist/check/asset-rules.js +163 -0
  15. package/dist/check/browser-sources.d.ts +20 -0
  16. package/dist/check/browser-sources.js +89 -0
  17. package/dist/check/console-rules.d.ts +6 -0
  18. package/dist/check/console-rules.js +40 -0
  19. package/dist/check/error-rules.d.ts +21 -0
  20. package/dist/check/error-rules.js +212 -0
  21. package/dist/check/form-rules.d.ts +12 -0
  22. package/dist/check/form-rules.js +69 -6
  23. package/dist/check/formatter-rules.d.ts +2 -0
  24. package/dist/check/formatter-rules.js +71 -0
  25. package/dist/check/i18n-rules.d.ts +27 -0
  26. package/dist/check/i18n-rules.js +353 -0
  27. package/dist/check/index.d.ts +15 -1
  28. package/dist/check/index.js +89 -11
  29. package/dist/check/mail-rules.d.ts +4 -0
  30. package/dist/check/mail-rules.js +72 -0
  31. package/dist/check/module-rules.d.ts +13 -0
  32. package/dist/check/module-rules.js +104 -0
  33. package/dist/check/packages.d.ts +8 -0
  34. package/dist/check/packages.js +16 -0
  35. package/dist/check/pool-rules.d.ts +8 -0
  36. package/dist/check/pool-rules.js +164 -0
  37. package/dist/check/problems.d.ts +6 -1
  38. package/dist/check/problems.js +100 -2
  39. package/dist/check/project-rules.d.ts +23 -3
  40. package/dist/check/project-rules.js +265 -23
  41. package/dist/check/seal-rules.d.ts +6 -0
  42. package/dist/check/seal-rules.js +68 -0
  43. package/dist/check/security-rules.d.ts +10 -0
  44. package/dist/check/security-rules.js +450 -0
  45. package/dist/check/shared-state-rules.d.ts +2 -0
  46. package/dist/check/shared-state-rules.js +114 -0
  47. package/dist/check/source-rules.d.ts +26 -3
  48. package/dist/check/source-rules.js +249 -28
  49. package/dist/check/structure-rules.d.ts +23 -0
  50. package/dist/check/structure-rules.js +502 -0
  51. package/dist/check/timer-rules.d.ts +7 -0
  52. package/dist/check/timer-rules.js +543 -0
  53. package/dist/check/typescript.d.ts +35 -5
  54. package/dist/check/typescript.js +163 -17
  55. package/dist/check/vite-rules.d.ts +2 -0
  56. package/dist/check/vite-rules.js +136 -0
  57. package/dist/check/wait-rules.d.ts +16 -0
  58. package/dist/check/wait-rules.js +337 -0
  59. package/dist/commands.d.ts +7 -0
  60. package/dist/commands.js +307 -31
  61. package/dist/config-diff.d.ts +42 -0
  62. package/dist/config-diff.js +289 -0
  63. package/dist/config-edit.d.ts +35 -2
  64. package/dist/config-edit.js +145 -13
  65. package/dist/config-init.d.ts +15 -0
  66. package/dist/config-init.js +61 -0
  67. package/dist/config-paths.d.ts +22 -0
  68. package/dist/config-paths.js +82 -0
  69. package/dist/config-show.d.ts +82 -0
  70. package/dist/config-show.js +563 -0
  71. package/dist/config-values.d.ts +8 -0
  72. package/dist/config-values.js +68 -0
  73. package/dist/configuration-scaffold.d.ts +42 -0
  74. package/dist/configuration-scaffold.js +258 -4
  75. package/dist/configuration.d.ts +63 -3
  76. package/dist/configuration.js +178 -4
  77. package/dist/deployment.d.ts +2 -0
  78. package/dist/deployment.js +111 -0
  79. package/dist/describe-db.d.ts +22 -0
  80. package/dist/describe-db.js +176 -0
  81. package/dist/describe.d.ts +92 -0
  82. package/dist/describe.js +417 -0
  83. package/dist/dev-compose.d.ts +20 -0
  84. package/dist/dev-compose.js +154 -0
  85. package/dist/dev-guard.js +44 -0
  86. package/dist/dev-host.d.ts +13 -0
  87. package/dist/dev-host.js +55 -0
  88. package/dist/dev-infra.d.ts +72 -4
  89. package/dist/dev-infra.js +237 -17
  90. package/dist/dev-watch.d.ts +34 -0
  91. package/dist/dev-watch.js +192 -0
  92. package/dist/diagnostics.d.ts +10 -7
  93. package/dist/diagnostics.js +22 -4
  94. package/dist/drift.d.ts +50 -0
  95. package/dist/drift.js +218 -0
  96. package/dist/env-file.d.ts +25 -1
  97. package/dist/env-file.js +64 -6
  98. package/dist/errors.d.ts +7 -1
  99. package/dist/example-config.d.ts +2 -0
  100. package/dist/example-config.js +3 -0
  101. package/dist/first-admin.d.ts +43 -0
  102. package/dist/first-admin.js +175 -0
  103. package/dist/formatter-ignore.d.ts +37 -0
  104. package/dist/formatter-ignore.js +344 -0
  105. package/dist/generator.d.ts +45 -3
  106. package/dist/generator.js +205 -15
  107. package/dist/guide.d.ts +1 -1
  108. package/dist/guide.js +42 -21
  109. package/dist/install-module.d.ts +29 -3
  110. package/dist/install-module.js +142 -13
  111. package/dist/install-plan.js +5 -3
  112. package/dist/layout.d.ts +12 -1
  113. package/dist/layout.js +19 -6
  114. package/dist/log-writer-control.d.ts +2 -0
  115. package/dist/log-writer-control.js +106 -27
  116. package/dist/log-writer.js +43 -10
  117. package/dist/logging-config.d.ts +8 -3
  118. package/dist/logging-config.js +19 -5
  119. package/dist/manifest.d.ts +29 -1
  120. package/dist/manifest.js +84 -6
  121. package/dist/merge-install-defaults.d.ts +0 -1
  122. package/dist/merge-install-defaults.js +13 -47
  123. package/dist/npm-client.d.ts +59 -4
  124. package/dist/npm-client.js +220 -16
  125. package/dist/npm-paths.js +18 -4
  126. package/dist/openapi.d.ts +27 -0
  127. package/dist/openapi.js +649 -0
  128. package/dist/option-fixes.d.ts +39 -0
  129. package/dist/option-fixes.js +130 -0
  130. package/dist/output.d.ts +1 -1
  131. package/dist/output.js +5 -5
  132. package/dist/plugin-lifecycle.d.ts +34 -2
  133. package/dist/plugin-lifecycle.js +206 -20
  134. package/dist/plugin-options.d.ts +19 -4
  135. package/dist/plugin-options.js +138 -6
  136. package/dist/plugins-command.js +44 -7
  137. package/dist/process-group.d.ts +39 -0
  138. package/dist/process-group.js +132 -0
  139. package/dist/project-guide.d.ts +19 -1
  140. package/dist/project-guide.js +119 -20
  141. package/dist/project-inspector.d.ts +15 -0
  142. package/dist/project-inspector.js +481 -33
  143. package/dist/project-state.d.ts +5 -0
  144. package/dist/project-state.js +8 -0
  145. package/dist/project.d.ts +203 -19
  146. package/dist/project.js +3016 -336
  147. package/dist/recipe-facts.d.ts +127 -0
  148. package/dist/recipe-facts.js +563 -0
  149. package/dist/recipe-login.d.ts +63 -0
  150. package/dist/recipe-login.js +464 -0
  151. package/dist/recipe-mail.d.ts +21 -0
  152. package/dist/recipe-mail.js +214 -0
  153. package/dist/recipe-members.d.ts +35 -0
  154. package/dist/recipe-members.js +178 -0
  155. package/dist/recipe-record.d.ts +82 -7
  156. package/dist/recipe-record.js +226 -22
  157. package/dist/recipe-reset.d.ts +2 -0
  158. package/dist/recipe-reset.js +117 -0
  159. package/dist/recipe-site.d.ts +48 -0
  160. package/dist/recipe-site.js +396 -0
  161. package/dist/recipe-step.d.ts +42 -0
  162. package/dist/recipe-step.js +89 -0
  163. package/dist/recipe.d.ts +56 -20
  164. package/dist/recipe.js +703 -131
  165. package/dist/registry.d.ts +98 -2
  166. package/dist/registry.js +171 -5
  167. package/dist/reloadable.d.ts +35 -0
  168. package/dist/reloadable.js +257 -0
  169. package/dist/server.d.ts +50 -4
  170. package/dist/server.js +790 -103
  171. package/dist/settings.d.ts +48 -0
  172. package/dist/settings.js +168 -0
  173. package/dist/site-auth.d.ts +39 -0
  174. package/dist/site-auth.js +195 -0
  175. package/dist/template-fixes.d.ts +26 -0
  176. package/dist/template-fixes.js +285 -0
  177. package/dist/test-reporter.d.ts +49 -0
  178. package/dist/test-reporter.js +190 -0
  179. package/dist/transaction.d.ts +67 -0
  180. package/dist/transaction.js +428 -46
  181. package/dist/verify-tests.d.ts +98 -0
  182. package/dist/verify-tests.js +449 -0
  183. package/dist/verify.d.ts +113 -4
  184. package/dist/verify.js +326 -28
  185. package/dist/view-choice.d.ts +58 -0
  186. package/dist/view-choice.js +131 -0
  187. package/dist/vite-watch.js +4 -2
  188. package/package.json +8 -6
package/catalog.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "$comment": "Generated by scripts/catalog/build.ts from package.json dokebiCatalog fields and packages/cli/catalog-src (D-0065). Do not edit; run node scripts/catalog/build.ts.",
3
3
  "schemaVersion": 1,
4
- "cli": "0.4.1",
4
+ "cli": "0.4.26",
5
5
  "core": {
6
6
  "package": "@dokebi/core",
7
- "version": "0.4.0"
7
+ "version": "0.4.2"
8
8
  },
9
9
  "capabilities": {
10
10
  "admin.ui": {
@@ -12,9 +12,16 @@
12
12
  "en": "admin dashboard crud backoffice"
13
13
  },
14
14
  "auth": {
15
- "summary": "login",
16
- "users and permissions (authentication and authorization)": null,
17
- "en": "auth authentication authorization login user password oauth jwt signup"
15
+ "summary": "session login, route protection and permissions on your own user table (authentication and authorization)",
16
+ "en": "auth authentication authorization login logout user password session signin"
17
+ },
18
+ "auth.permissions": {
19
+ "summary": "roles, grades and scoped permissions (all, subtree, direct, own over a holding tree) checked from memory",
20
+ "en": "permissions rbac roles grades scope access control authorization admin reseller tree"
21
+ },
22
+ "background.outbox": {
23
+ "summary": "durable background work in the app database (enqueue in the transaction, fair claims across owners, per-destination breakers, dead items with resend, retention)",
24
+ "en": "outbox background work delivery webhook export cleanup retry backoff breaker transactional"
18
25
  },
19
26
  "cache.local": {
20
27
  "summary": "in-process memory cache (LRU)",
@@ -25,23 +32,28 @@
25
32
  "en": "cache redis shared"
26
33
  },
27
34
  "cli.project": {
28
- "summary": "commands to create a project",
29
- "install plugins and verify it": null,
35
+ "summary": "commands to create a project, install plugins and verify it",
30
36
  "en": "command line scaffold install verify"
31
37
  },
32
38
  "config.yaml": {
33
39
  "summary": "read and validate dokebi.yaml (a required part of the core)",
34
40
  "en": "yaml configuration"
35
41
  },
42
+ "content.rich-text": {
43
+ "summary": "rich text documents from an editor checked whole and written as safe HTML, text and excerpts",
44
+ "en": "rich text editor tiptap prosemirror wysiwyg html sanitize body post content"
45
+ },
36
46
  "core.runtime": {
37
- "summary": "application lifecycle",
38
- "plugin registration": null,
39
- "logger and shared libraries": null,
47
+ "summary": "application lifecycle, plugin registration, logger and shared libraries",
40
48
  "en": "application lifecycle plugin registry logger"
41
49
  },
42
50
  "csrf": {
43
- "summary": "issue and check CSRF tokens",
44
- "en": "csrf xsrf token"
51
+ "summary": "CSRF protection for forms and scripts (origin check and tokens)",
52
+ "en": "csrf xsrf token forgery form protection"
53
+ },
54
+ "data.tree": {
55
+ "summary": "trees in your own tables (paths, subtree moves, sibling order, ancestors, pages in tree order)",
56
+ "en": "tree hierarchy categories parent children ancestors descendants subtree nested path"
45
57
  },
46
58
  "db.connection": {
47
59
  "summary": "named database connections (pools) with readiness checks and shutdown",
@@ -75,23 +87,40 @@
75
87
  "summary": "SQLite file database",
76
88
  "en": "sqlite sqlite3 file database"
77
89
  },
90
+ "email-verification": {
91
+ "summary": "e-mail address verification after sign-up",
92
+ "en": "email verification confirm address signup"
93
+ },
78
94
  "events.bus": {
79
95
  "summary": "event bus (in process or over NATS)",
80
96
  "en": "event bus pubsub publish subscribe"
81
97
  },
98
+ "files.records": {
99
+ "summary": "a record and owner of every stored file (claim, release, removal after commit, sweep of unowned files, read rules per owner)",
100
+ "en": "files uploads attachments records owner cleanup orphan download thumbnail"
101
+ },
102
+ "font.korean": {
103
+ "summary": "Korean font files for writing PDF files (Hangul, Hanja, kana and Latin)",
104
+ "en": "font korean hangul cjk noto typeface glyph"
105
+ },
106
+ "geoip.lookup": {
107
+ "summary": "offline IP geolocation (country, region, city, time zone, ISP) from operator-provided MaxMind DB files such as GeoLite2 or DB-IP Lite, in memory without a network call",
108
+ "en": "geoip geolocation ip location country city region asn isp maxmind geolite2 mmdb dbip"
109
+ },
82
110
  "health.checks": {
83
- "summary": "live",
84
- "ready and draining state with readiness checks": null,
111
+ "summary": "live, ready and draining state with readiness checks",
85
112
  "en": "health readiness liveness"
86
113
  },
87
114
  "i18n": {
88
115
  "summary": "multiple languages (translated strings)",
89
116
  "en": "i18n translation locale"
90
117
  },
118
+ "image.thumbnail": {
119
+ "summary": "server-side image thumbnails (WebP) of stored files, made once and deleted with their source",
120
+ "en": "image thumbnail resize preview webp picture photo sharp"
121
+ },
91
122
  "job.durable": {
92
- "summary": "at-least-once job queue (JetStream",
93
- "retries": null,
94
- "DLQ)": null,
123
+ "summary": "at-least-once job queue (JetStream, retries, DLQ)",
95
124
  "en": "job queue background task retry dlq"
96
125
  },
97
126
  "lock.local": {
@@ -102,26 +131,88 @@
102
131
  "summary": "Redis lease lock (fencing tokens)",
103
132
  "en": "distributed lock redis lease"
104
133
  },
134
+ "log.activity": {
135
+ "summary": "activity log of what members do (sign-ins, actions) with address and device, masked failed logins, retention",
136
+ "en": "activity log login history sign in ip address device member actions"
137
+ },
138
+ "log.audit": {
139
+ "summary": "audit log of changes (written in the change's transaction, a declared code tree, the actor from auth or an OAuth grant, masked values, keyset lists)",
140
+ "en": "audit log history changes who changed operator admin trail"
141
+ },
142
+ "logging.network": {
143
+ "summary": "ship log records to a receiver over HTTPS in batches (NDJSON for Dokebi Studio, OTLP/HTTP JSON for an OpenTelemetry collector) with retries and bounded buffers",
144
+ "en": "log shipping network http https ndjson otlp opentelemetry collector studio batch"
145
+ },
146
+ "logging.redaction": {
147
+ "summary": "secrets removed from every log record before any target (passwords, tokens, cookies, chosen fields)",
148
+ "en": "redact redaction mask secrets password token log"
149
+ },
150
+ "logging.rotation": {
151
+ "summary": "log files rotated by size and time with a kept count and age",
152
+ "en": "log rotation rotate files logrotate"
153
+ },
154
+ "logging.targets": {
155
+ "summary": "the app logger writes JSON to stdout, rotated files and readable development lines at once without blocking the app",
156
+ "en": "logging logger log pino stdout file pretty production"
157
+ },
105
158
  "mail": {
106
- "summary": "send mail (SMTP)",
107
- "en": "mail email smtp"
159
+ "summary": "mail templates, SMTP and SES, queued delivery",
160
+ "en": "mail email smtp ses templates"
108
161
  },
109
162
  "markdown": {
110
163
  "summary": "convert Markdown to HTML",
111
164
  "en": "markdown md html"
112
165
  },
166
+ "mcp.server": {
167
+ "summary": "MCP server so AI clients call your tools over stateless Streamable HTTP with OAuth tokens (scopes per tool, result envelope, per-connection limits)",
168
+ "en": "mcp model context protocol ai tools claude chatgpt connector server"
169
+ },
170
+ "messaging.bulk-send": {
171
+ "summary": "bulk sends to many recipients over mail, notes or other channels with recipient rows, scheduling, progress, stop and resend of failures",
172
+ "en": "bulk send mass mail newsletter broadcast notice messages campaign recipients schedule"
173
+ },
113
174
  "messaging.nats": {
114
175
  "summary": "named NATS connections and JetStream publishing",
115
176
  "en": "nats jetstream messaging"
116
177
  },
178
+ "metrics.http": {
179
+ "summary": "per-route HTTP response metrics",
180
+ "en": "metrics prometheus monitoring latency status"
181
+ },
182
+ "oauth": {
183
+ "summary": "social login with Google or Apple (sign in with another provider)",
184
+ "en": "oauth social login google apple sign in with"
185
+ },
186
+ "oauth.server": {
187
+ "summary": "OAuth 2.1 authorization server so AI clients (MCP) connect for a signed-in user (PKCE, refresh rotation, revocation, protected resource metadata)",
188
+ "en": "oauth2 authorization server pkce mcp connector token grant consent"
189
+ },
190
+ "oidc.client": {
191
+ "summary": "sign in with any OpenID Connect provider (company SSO, GitHub, Keycloak, Okta, Azure AD)",
192
+ "en": "oidc openid connect sso github keycloak okta azure entra provider external login"
193
+ },
117
194
  "orm": {
118
- "summary": "ORM",
119
- "query builder and model mapping": null,
195
+ "summary": "ORM, query builder and model mapping",
120
196
  "en": "orm query builder model"
121
197
  },
198
+ "password-reset": {
199
+ "summary": "password reset by e-mail (forgotten password links)",
200
+ "en": "password reset forgot recovery link"
201
+ },
202
+ "pdf.generate": {
203
+ "summary": "create PDF files (documents, invoices, reports)",
204
+ "en": "pdf generate create make export render document invoice report print"
205
+ },
206
+ "pdf.text": {
207
+ "summary": "text of stored PDF files as page parts, with the page count and whether there is any text, extracted once in bounded worker threads",
208
+ "en": "pdf text extract read document page attachment pdfjs"
209
+ },
210
+ "pdf.thumbnail": {
211
+ "summary": "a WebP thumbnail of the first page of a stored PDF file, made once and deleted with its source",
212
+ "en": "pdf thumbnail preview first page render webp"
213
+ },
122
214
  "process.cluster": {
123
- "summary": "run several node:cluster workers",
124
- "watch them and replace them one by one": null,
215
+ "summary": "run several node:cluster workers, watch them and replace them one by one",
125
216
  "en": "cluster multi process workers"
126
217
  },
127
218
  "rate-limit": {
@@ -129,8 +220,7 @@
129
220
  "en": "rate limit throttle"
130
221
  },
131
222
  "realtime.hub": {
132
- "summary": "hub of WebSocket clients",
133
- "rooms and broadcast": null,
223
+ "summary": "hub of WebSocket clients, rooms and broadcast",
134
224
  "en": "websocket hub rooms broadcast"
135
225
  },
136
226
  "realtime.sse": {
@@ -138,8 +228,7 @@
138
228
  "en": "sse server sent events eventsource"
139
229
  },
140
230
  "realtime.websocket": {
141
- "summary": "WebSocket routes",
142
- "rooms and broadcast (across servers and workers)": null,
231
+ "summary": "WebSocket routes, rooms and broadcast (across servers and workers)",
143
232
  "en": "websocket socket realtime chat"
144
233
  },
145
234
  "redis.connection": {
@@ -150,6 +239,10 @@
150
239
  "summary": "Redis leader election and incremental state",
151
240
  "en": "redis leader election coordination"
152
241
  },
242
+ "reports.rollups": {
243
+ "summary": "report roll-ups (facts folded into hour, day and month rows per time zone, read with an as-of time and a report budget)",
244
+ "en": "reports statistics stats rollups aggregates dashboard hourly daily monthly totals timezone"
245
+ },
153
246
  "rss": {
154
247
  "summary": "RSS and Atom feeds",
155
248
  "en": "rss atom feed"
@@ -163,10 +256,25 @@
163
256
  "en": "search full text index"
164
257
  },
165
258
  "security.seal": {
166
- "summary": "sealing of transport bodies (obfuscation",
167
- "replay protection) and a browser client": null,
259
+ "summary": "sealing of transport bodies (obfuscation, replay protection) and a browser client",
168
260
  "en": "seal obfuscation replay encryption"
169
261
  },
262
+ "settings.runtime": {
263
+ "summary": "settings operators change while the app runs (stored in the database, checked by type and rules, common and personal values, every worker refreshed)",
264
+ "en": "runtime settings admin options preferences operator values site configuration personal"
265
+ },
266
+ "static.files": {
267
+ "summary": "a folder served under a path prefix (update feeds, installers, downloads) with Range, ETag and cache rules",
268
+ "en": "static files folder download update feed appcast installer range etag"
269
+ },
270
+ "time.site-zone": {
271
+ "summary": "an application time zone changed while the app runs and day boundaries computed the same on the server and in the browser",
272
+ "en": "timezone time zone site day boundary utc range calendar today dst summer time"
273
+ },
274
+ "two-factor": {
275
+ "summary": "two-factor login (TOTP, recovery codes)",
276
+ "en": "two factor 2fa mfa totp otp authenticator"
277
+ },
170
278
  "upload": {
171
279
  "summary": "receive and store file uploads (multipart)",
172
280
  "en": "upload multipart file storage"
@@ -195,23 +303,32 @@
195
303
  "summary": "controller registry per app",
196
304
  "en": "controller mvc"
197
305
  },
306
+ "web.cors": {
307
+ "summary": "cross-origin browser access (CORS) with allowed origins, methods, headers and credentials per app",
308
+ "en": "cors cross origin preflight access-control-allow-origin"
309
+ },
198
310
  "web.http": {
199
- "summary": "HTTP server (Fastify) with 503 before ready",
200
- "drain and health paths": null,
311
+ "summary": "HTTP server (Fastify) with 503 before ready, drain and health paths",
201
312
  "en": "http server fastify web"
202
313
  },
203
314
  "web.modules": {
204
315
  "summary": "feature modules (src/shared/modules)",
205
316
  "en": "feature module"
206
317
  },
318
+ "web.overload": {
319
+ "summary": "load shedding with 503 and Retry-After when the event loop stalls or memory runs high, health routes served",
320
+ "en": "overload load shedding under pressure event loop delay backpressure 503"
321
+ },
207
322
  "web.routing": {
208
323
  "summary": "routes grouped under a prefix (mount)",
209
324
  "en": "router routes prefix"
210
325
  },
326
+ "web.security-headers": {
327
+ "summary": "security headers on by default (CSP, HSTS over HTTPS, X-Frame-Options, nosniff, Referrer-Policy) with per-route changes",
328
+ "en": "security headers csp content security policy hsts helmet clickjacking frame options nosniff"
329
+ },
211
330
  "web.session": {
212
- "summary": "server-side sessions (signed cookie",
213
- "memory or redis store": null,
214
- "flash)": null,
331
+ "summary": "server-side sessions (signed cookie, memory or redis store, flash)",
215
332
  "en": "session cookie flash"
216
333
  },
217
334
  "worker.thread": {
@@ -223,7 +340,7 @@
223
340
  {
224
341
  "id": "dokebi-app",
225
342
  "package": "@dokebi/app",
226
- "version": "0.4.1",
343
+ "version": "0.4.5",
227
344
  "kind": "plugin",
228
345
  "summary": "Several apps chosen by request domain (src/apps/<app>) with a template each; installs web, controller and template-ejs with it",
229
346
  "description": "Multiple apps per project for Dokebi: pick the app and template by request domain, with EJS and Inertia skeletons.",
@@ -247,12 +364,13 @@
247
364
  ],
248
365
  "recipes": [
249
366
  "api",
250
- "blog"
367
+ "blog",
368
+ "site"
251
369
  ],
252
370
  "requires": [
253
371
  {
254
372
  "id": "dokebi-web",
255
- "range": "^0.4.1",
373
+ "range": "^0.4.3",
256
374
  "package": "@dokebi/web",
257
375
  "automatic": true
258
376
  },
@@ -277,6 +395,10 @@
277
395
  {
278
396
  "id": "dokebi-session",
279
397
  "range": "^0.4.0"
398
+ },
399
+ {
400
+ "id": "dokebi-static",
401
+ "range": "^0.1.0"
280
402
  }
281
403
  ],
282
404
  "optionKeys": [
@@ -290,10 +412,233 @@
290
412
  },
291
413
  "install": "npx dokebi add @dokebi/app"
292
414
  },
415
+ {
416
+ "id": "dokebi-audit",
417
+ "package": "@dokebi/audit",
418
+ "version": "0.1.0",
419
+ "kind": "plugin",
420
+ "summary": "Audit and activity logs in your database: written in the change's transaction, a declared code tree, the actor from auth or an OAuth grant (AI clients by name), masking before the write, indexed keyset lists, retention",
421
+ "description": "Audit and activity logs for Dokebi: rows written in the change's transaction, a declared code tree, the actor from auth or an OAuth grant, masking before the write, indexed keyset lists and retention.",
422
+ "keywords": [
423
+ "dokebi",
424
+ "dokebi-plugin",
425
+ "audit",
426
+ "activity",
427
+ "log",
428
+ "database"
429
+ ],
430
+ "maturity": "experimental",
431
+ "core": "^0.4.2",
432
+ "provides": [
433
+ "log.activity",
434
+ "log.audit"
435
+ ],
436
+ "variants": [],
437
+ "doesNot": [
438
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
439
+ "No free-text search over labels and reasons; filters by code, actor, target, owner, address and time",
440
+ "Does not decide who may read the logs: the application's routes do",
441
+ "Does not create tables at start (the migration dokebi add writes)"
442
+ ],
443
+ "recipes": [],
444
+ "requires": [
445
+ {
446
+ "id": "dokebi-db",
447
+ "range": "^0.4.1",
448
+ "package": "@dokebi/db",
449
+ "automatic": true
450
+ }
451
+ ],
452
+ "optional": [
453
+ {
454
+ "id": "dokebi-geoip",
455
+ "range": "^0.1.0"
456
+ }
457
+ ],
458
+ "optionKeys": [
459
+ "activity",
460
+ "audit",
461
+ "connection",
462
+ "list",
463
+ "maxChangesBytes",
464
+ "pool",
465
+ "prune"
466
+ ],
467
+ "docs": {
468
+ "ai": "AI.md",
469
+ "readme": "README.md"
470
+ },
471
+ "install": "npx dokebi add @dokebi/audit"
472
+ },
473
+ {
474
+ "id": "dokebi-auth",
475
+ "package": "@dokebi/auth",
476
+ "version": "0.4.1",
477
+ "kind": "plugin",
478
+ "summary": "Login on your own user table: sessions, remember-me, API tokens, route protection, authorize hook, password hashing, login throttling, realms; optional Google/Apple social login",
479
+ "description": "Login for @dokebi/web on your own user table through an extension class: sessions, remember-me, API tokens (personal, access/refresh), route protection, authorization hook, password hashing, login throttling, realms.",
480
+ "keywords": [
481
+ "dokebi",
482
+ "dokebi-plugin",
483
+ "web",
484
+ "auth",
485
+ "login",
486
+ "password",
487
+ "token"
488
+ ],
489
+ "maturity": "experimental",
490
+ "core": "^0.4.2",
491
+ "provides": [
492
+ "auth",
493
+ "email-verification",
494
+ "oauth",
495
+ "password-reset"
496
+ ],
497
+ "variants": [],
498
+ "doesNot": [
499
+ "Does not own a user table, roles or a sign-up screen (your extension class maps your rows)",
500
+ "No two-factor login yet; reset and email-confirmed signup require mail and job",
501
+ "Does not issue CSRF tokens",
502
+ "No JWT: API tokens are opaque and checked against the token store",
503
+ "Is not an OAuth authorization server (that is @dokebi/oauth) and supports no arbitrary OIDC providers; social login supports Google and Apple"
504
+ ],
505
+ "recipes": [
506
+ "blog",
507
+ "site"
508
+ ],
509
+ "requires": [
510
+ {
511
+ "id": "dokebi-web",
512
+ "range": "^0.4.3"
513
+ },
514
+ {
515
+ "id": "dokebi-session",
516
+ "range": "^0.4.4"
517
+ }
518
+ ],
519
+ "optional": [
520
+ {
521
+ "id": "dokebi-redis",
522
+ "range": "^0.4.0"
523
+ },
524
+ {
525
+ "id": "dokebi-db",
526
+ "range": "^0.4.0"
527
+ },
528
+ {
529
+ "id": "dokebi-csrf",
530
+ "range": "^0.4.0"
531
+ },
532
+ {
533
+ "id": "dokebi-mail",
534
+ "range": "^0.4.0"
535
+ }
536
+ ],
537
+ "optionKeys": [
538
+ "apps",
539
+ "bearerMaxBlocked",
540
+ "cacheMs",
541
+ "default",
542
+ "hashAlgorithm",
543
+ "hashWaitTimeoutMs",
544
+ "homePath",
545
+ "live",
546
+ "logRepeatWindowMs",
547
+ "loginPath",
548
+ "mailLinkMaxRunning",
549
+ "mailLinkMaxWaiting",
550
+ "maxCachedUsers",
551
+ "maxConcurrentHashes",
552
+ "maxPasswordBytes",
553
+ "maxQueuedHashes",
554
+ "maxStampMemo",
555
+ "passwordConfirmMs",
556
+ "passwordReset",
557
+ "realm",
558
+ "reauthenticatePath",
559
+ "rememberMs",
560
+ "rememberRaceMs",
561
+ "secrets",
562
+ "secretsEnv",
563
+ "signup",
564
+ "storeRetryAfterMs",
565
+ "throttle",
566
+ "tokenMaxTouched",
567
+ "tokenTouchIntervalMs",
568
+ "tokens",
569
+ "verifyMaxMemoryMiB",
570
+ "via"
571
+ ],
572
+ "docs": {
573
+ "ai": "AI.md",
574
+ "readme": "README.md"
575
+ },
576
+ "install": "npx dokebi add @dokebi/auth"
577
+ },
578
+ {
579
+ "id": "dokebi-bulk-send",
580
+ "package": "@dokebi/bulk-send",
581
+ "version": "0.1.0",
582
+ "kind": "plugin",
583
+ "summary": "Bulk sends over @dokebi/outbox: a send row, recipient rows per person and channel fixed when it goes (excluded and failed with reasons), channels and audiences your app registers (mail through @dokebi/mail, an inbox of your own), scheduling and cancel, progress per channel, stop, and failed deliveries sent again",
584
+ "description": "Bulk sends for Dokebi over @dokebi/outbox: recipient rows fixed when a send goes, channels and audiences your app registers, scheduling and cancel, progress per channel, stop, and failures sent again.",
585
+ "keywords": [
586
+ "dokebi",
587
+ "dokebi-plugin",
588
+ "bulk",
589
+ "mail",
590
+ "messages",
591
+ "newsletter",
592
+ "outbox"
593
+ ],
594
+ "maturity": "experimental",
595
+ "core": "^0.4.2",
596
+ "provides": [
597
+ "messaging.bulk-send"
598
+ ],
599
+ "variants": [],
600
+ "doesNot": [
601
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
602
+ "No templates or inbox of its own: your channels send (mail.send) or write your inbox rows",
603
+ "No bounce processing: a delivery error your channel throws marks the row",
604
+ "Does not create tables at start (the migration dokebi add writes)"
605
+ ],
606
+ "recipes": [],
607
+ "requires": [
608
+ {
609
+ "id": "dokebi-db",
610
+ "range": "^0.4.1",
611
+ "package": "@dokebi/db",
612
+ "automatic": true
613
+ },
614
+ {
615
+ "id": "dokebi-outbox",
616
+ "range": "^0.1.0",
617
+ "package": "@dokebi/outbox",
618
+ "automatic": true
619
+ }
620
+ ],
621
+ "optional": [],
622
+ "optionKeys": [
623
+ "connection",
624
+ "deliverBatch",
625
+ "deliverConcurrency",
626
+ "maxContentBytes",
627
+ "maxPage",
628
+ "maxScheduleDays",
629
+ "maxTargetBytes",
630
+ "prepareBatch"
631
+ ],
632
+ "docs": {
633
+ "ai": "AI.md",
634
+ "readme": "README.md"
635
+ },
636
+ "install": "npx dokebi add @dokebi/bulk-send"
637
+ },
293
638
  {
294
639
  "id": "dokebi-cache",
295
640
  "package": "@dokebi/cache",
296
- "version": "0.4.0",
641
+ "version": "0.4.1",
297
642
  "kind": "plugin",
298
643
  "summary": "Named cache backends (local LRU by default; redis through the @dokebi/cache-redis driver)",
299
644
  "description": "Named cache backends for Dokebi: in-process LRU or a shared Redis driver.",
@@ -305,7 +650,7 @@
305
650
  "redis"
306
651
  ],
307
652
  "maturity": "preview",
308
- "core": "^0.4.0",
653
+ "core": "^0.4.2",
309
654
  "provides": [
310
655
  "cache.local"
311
656
  ],
@@ -323,7 +668,8 @@
323
668
  }
324
669
  ],
325
670
  "optionKeys": [
326
- "backends"
671
+ "backends",
672
+ "retryAfterMs"
327
673
  ],
328
674
  "docs": {
329
675
  "ai": "AI.md",
@@ -334,7 +680,7 @@
334
680
  {
335
681
  "id": null,
336
682
  "package": "@dokebi/cache-redis",
337
- "version": "0.4.0",
683
+ "version": "0.4.1",
338
684
  "kind": "driver",
339
685
  "summary": "Redis driver of @dokebi/cache (not a plugin: install it with npm install)",
340
686
  "description": "Redis driver for @dokebi/cache (not a plugin; install with npm).",
@@ -367,7 +713,7 @@
367
713
  {
368
714
  "id": null,
369
715
  "package": "@dokebi/cli",
370
- "version": "0.4.1",
716
+ "version": "0.4.26",
371
717
  "kind": "cli",
372
718
  "summary": "Commands to create a project, browse the plugin catalog, run recipes, install plugins and verify (dokebi)",
373
719
  "description": "Project scaffolding, plugin catalog, recipes, install and verification CLI for Dokebi.",
@@ -390,7 +736,8 @@
390
736
  ],
391
737
  "recipes": [
392
738
  "api",
393
- "blog"
739
+ "blog",
740
+ "site"
394
741
  ],
395
742
  "requires": [],
396
743
  "optional": [],
@@ -404,7 +751,7 @@
404
751
  {
405
752
  "id": null,
406
753
  "package": "@dokebi/cluster",
407
- "version": "0.4.0",
754
+ "version": "0.4.1",
408
755
  "kind": "library",
409
756
  "summary": "Runner that starts several app workers from a node:cluster parent process, watches them and replaces them one by one",
410
757
  "description": "node:cluster runner for Dokebi: starts, watches and rolls independent application workers.",
@@ -416,7 +763,7 @@
416
763
  "rolling-restart"
417
764
  ],
418
765
  "maturity": "preview",
419
- "core": "^0.4.0",
766
+ "core": "^0.4.2",
420
767
  "provides": [
421
768
  "process.cluster"
422
769
  ],
@@ -438,7 +785,7 @@
438
785
  {
439
786
  "id": "dokebi-config",
440
787
  "package": "@dokebi/config",
441
- "version": "0.4.0",
788
+ "version": "0.4.2",
442
789
  "kind": "required",
443
790
  "summary": "Required part of the core that reads and validates dokebi.yaml into read-only settings per app (installed automatically)",
444
791
  "description": "Validated immutable YAML configuration for Dokebi applications.",
@@ -460,7 +807,8 @@
460
807
  ],
461
808
  "recipes": [
462
809
  "api",
463
- "blog"
810
+ "blog",
811
+ "site"
464
812
  ],
465
813
  "requires": [],
466
814
  "optional": [],
@@ -474,7 +822,7 @@
474
822
  {
475
823
  "id": "dokebi-controller",
476
824
  "package": "@dokebi/controller",
477
- "version": "0.4.0",
825
+ "version": "0.4.2",
478
826
  "kind": "plugin",
479
827
  "summary": "Registry that creates each controller object once per app and hands it out with its type",
480
828
  "description": "Per-app controller registry for Dokebi: build controllers once and read them with their types.",
@@ -497,7 +845,8 @@
497
845
  ],
498
846
  "recipes": [
499
847
  "api",
500
- "blog"
848
+ "blog",
849
+ "site"
501
850
  ],
502
851
  "requires": [],
503
852
  "optional": [],
@@ -511,7 +860,7 @@
511
860
  {
512
861
  "id": null,
513
862
  "package": "@dokebi/core",
514
- "version": "0.4.0",
863
+ "version": "0.4.2",
515
864
  "kind": "core",
516
865
  "summary": "Application core: settings loading, plugin registration, lifecycle, logger and shared libraries (required)",
517
866
  "description": "Minimal composable Node.js application and plugin lifecycle framework.",
@@ -534,7 +883,8 @@
534
883
  ],
535
884
  "recipes": [
536
885
  "api",
537
- "blog"
886
+ "blog",
887
+ "site"
538
888
  ],
539
889
  "requires": [],
540
890
  "optional": [],
@@ -545,10 +895,119 @@
545
895
  },
546
896
  "install": "automatic (dokebi init)"
547
897
  },
898
+ {
899
+ "id": "dokebi-cors",
900
+ "package": "@dokebi/cors",
901
+ "version": "0.1.0",
902
+ "kind": "plugin",
903
+ "summary": "CORS: the origins, methods, headers and credentials a browser page on another origin may use, per app of @dokebi/app, preflight answered before authentication",
904
+ "description": "CORS for @dokebi/web on @fastify/cors: allowed origins, methods, headers and credentials in dokebi.yaml, per app of @dokebi/app, preflight answered before auth.",
905
+ "keywords": [
906
+ "dokebi",
907
+ "dokebi-plugin",
908
+ "web",
909
+ "cors",
910
+ "security"
911
+ ],
912
+ "maturity": "experimental",
913
+ "core": "^0.4.2",
914
+ "provides": [
915
+ "web.cors"
916
+ ],
917
+ "variants": [],
918
+ "doesNot": [
919
+ "Not an access control: CORS only tells browsers which pages may read answers; servers and scripts ignore it",
920
+ "Does not change CSRF, session cookies (SameSite) or the origin checks of SSE and WebSocket routes: configure those where they live"
921
+ ],
922
+ "recipes": [],
923
+ "requires": [
924
+ {
925
+ "id": "dokebi-web",
926
+ "range": "^0.4.3",
927
+ "package": "@dokebi/web",
928
+ "automatic": true
929
+ }
930
+ ],
931
+ "optional": [],
932
+ "optionKeys": [
933
+ "allowedHeaders",
934
+ "apps",
935
+ "credentials",
936
+ "exposedHeaders",
937
+ "maxAgeSec",
938
+ "methods",
939
+ "origins",
940
+ "routeIds"
941
+ ],
942
+ "docs": {
943
+ "ai": "AI.md",
944
+ "readme": "README.md"
945
+ },
946
+ "install": "npx dokebi add @dokebi/cors"
947
+ },
948
+ {
949
+ "id": "dokebi-csrf",
950
+ "package": "@dokebi/csrf",
951
+ "version": "0.4.0",
952
+ "kind": "plugin",
953
+ "summary": "CSRF protection for web forms and scripts: origin check plus a signed, masked token (EJS field, Inertia XSRF-TOKEN cookie)",
954
+ "description": "CSRF protection for @dokebi/web: origin check and signed, masked double-submit tokens.",
955
+ "keywords": [
956
+ "dokebi",
957
+ "dokebi-plugin",
958
+ "web",
959
+ "csrf",
960
+ "security"
961
+ ],
962
+ "maturity": "experimental",
963
+ "core": "^0.4.2",
964
+ "provides": [
965
+ "csrf"
966
+ ],
967
+ "variants": [],
968
+ "doesNot": [
969
+ "No login or user accounts (that is dokebi-auth)",
970
+ "Does not parse multipart bodies: a multipart form sends the token in the header or the query string"
971
+ ],
972
+ "recipes": [
973
+ "blog",
974
+ "site"
975
+ ],
976
+ "requires": [
977
+ {
978
+ "id": "dokebi-web",
979
+ "range": "^0.4.3",
980
+ "package": "@dokebi/web",
981
+ "automatic": true
982
+ }
983
+ ],
984
+ "optional": [
985
+ {
986
+ "id": "dokebi-session",
987
+ "range": "^0.4.0"
988
+ }
989
+ ],
990
+ "optionKeys": [
991
+ "cookie",
992
+ "exclude",
993
+ "field",
994
+ "header",
995
+ "logRepeatWindowMs",
996
+ "originCheck",
997
+ "secrets",
998
+ "secretsEnv",
999
+ "trustedOrigins"
1000
+ ],
1001
+ "docs": {
1002
+ "ai": "AI.md",
1003
+ "readme": "README.md"
1004
+ },
1005
+ "install": "npx dokebi add @dokebi/csrf"
1006
+ },
548
1007
  {
549
1008
  "id": "dokebi-db",
550
1009
  "package": "@dokebi/db",
551
- "version": "0.4.0",
1010
+ "version": "0.4.1",
552
1011
  "kind": "plugin",
553
1012
  "summary": "Named database connections (pools) with readiness checks and shutdown; you use the driver API as it is",
554
1013
  "description": "Named database connections for Dokebi: create, readiness check and close pools of pg, mysql2, mariadb or mongodb.",
@@ -584,6 +1043,15 @@
584
1043
  "version": "8.23.1"
585
1044
  },
586
1045
  "migrate": true,
1046
+ "connectionOptions": {
1047
+ "max": 10,
1048
+ "waitTimeoutMs": 3000,
1049
+ "idleTimeoutMs": 10000,
1050
+ "statementTimeoutMs": 10000,
1051
+ "queryTimeoutMs": 15000,
1052
+ "checkTimeoutMs": 5000,
1053
+ "logRepeatWindowMs": 10000
1054
+ },
587
1055
  "devInfra": {
588
1056
  "docker": {
589
1057
  "image": "postgres:16.15-alpine",
@@ -614,6 +1082,16 @@
614
1082
  "version": "3.24.5"
615
1083
  },
616
1084
  "migrate": true,
1085
+ "connectionOptions": {
1086
+ "max": 10,
1087
+ "waitTimeoutMs": 3000,
1088
+ "maxWaiting": 4096,
1089
+ "connectTimeoutMs": 3000,
1090
+ "idleTimeoutMs": 10000,
1091
+ "statementTimeoutMs": 10000,
1092
+ "checkTimeoutMs": 5000,
1093
+ "logRepeatWindowMs": 10000
1094
+ },
617
1095
  "devInfra": {
618
1096
  "docker": {
619
1097
  "image": "mysql:8",
@@ -646,6 +1124,16 @@
646
1124
  "version": "3.5.4"
647
1125
  },
648
1126
  "migrate": true,
1127
+ "connectionOptions": {
1128
+ "max": 10,
1129
+ "waitTimeoutMs": 3000,
1130
+ "connectTimeoutMs": 3000,
1131
+ "initializationTimeoutMs": 3000,
1132
+ "idleTimeoutMs": 10000,
1133
+ "statementTimeoutMs": 10000,
1134
+ "checkTimeoutMs": 5000,
1135
+ "logRepeatWindowMs": 10000
1136
+ },
649
1137
  "devInfra": {
650
1138
  "docker": {
651
1139
  "image": "mariadb:11",
@@ -675,6 +1163,15 @@
675
1163
  "version": "7.7.0"
676
1164
  },
677
1165
  "migrate": true,
1166
+ "connectionOptions": {
1167
+ "max": 10,
1168
+ "waitTimeoutMs": 3000,
1169
+ "connectTimeoutMs": 3000,
1170
+ "serverSelectionTimeoutMs": 3000,
1171
+ "idleTimeoutMs": 10000,
1172
+ "queryTimeoutMs": 15000,
1173
+ "checkTimeoutMs": 5000
1174
+ },
678
1175
  "devInfra": {
679
1176
  "docker": {
680
1177
  "image": "mongo:7",
@@ -713,7 +1210,10 @@
713
1210
  }
714
1211
  ],
715
1212
  "optionKeys": [
716
- "connections"
1213
+ "busyRetryAfterMs",
1214
+ "connections",
1215
+ "timeoutRetryAfterMs",
1216
+ "unavailableRetryAfterMs"
717
1217
  ],
718
1218
  "docs": {
719
1219
  "ai": "AI.md",
@@ -724,7 +1224,7 @@
724
1224
  {
725
1225
  "id": "dokebi-event-bus",
726
1226
  "package": "@dokebi/event-bus",
727
- "version": "0.4.0",
1227
+ "version": "0.4.2",
728
1228
  "kind": "plugin",
729
1229
  "summary": "Named event buses (local or NATS, at most once)",
730
1230
  "description": "Named event buses for Dokebi: in-process dispatch or NATS Core delivery between processes.",
@@ -736,7 +1236,7 @@
736
1236
  "nats"
737
1237
  ],
738
1238
  "maturity": "preview",
739
- "core": "^0.4.0",
1239
+ "core": "^0.4.2",
740
1240
  "provides": [
741
1241
  "events.bus"
742
1242
  ],
@@ -753,7 +1253,8 @@
753
1253
  }
754
1254
  ],
755
1255
  "optionKeys": [
756
- "buses"
1256
+ "buses",
1257
+ "logRepeatWindowMs"
757
1258
  ],
758
1259
  "docs": {
759
1260
  "ai": "AI.md",
@@ -762,17 +1263,173 @@
762
1263
  "install": "npx dokebi add @dokebi/event-bus"
763
1264
  },
764
1265
  {
765
- "id": "dokebi-health",
766
- "package": "@dokebi/health",
767
- "version": "0.4.0",
1266
+ "id": "dokebi-files",
1267
+ "package": "@dokebi/files",
1268
+ "version": "0.1.0",
768
1269
  "kind": "plugin",
769
- "summary": "Collects the live, ready and draining state of the app and the readiness checks of plugins (web serves the HTTP paths)",
770
- "description": "Liveness, readiness and draining state with plugin readiness checks for Dokebi.",
1270
+ "summary": "A record of every stored file: uploads recorded without an owner, claimed by a feature in its transaction, removals whose bytes go after the commit through @dokebi/outbox, unowned files swept after a grace, read rules per owner type in front of downloads and thumbnails, lists by owner, kind and time",
1271
+ "description": "A record of every stored file for Dokebi: owners claimed in the caller's transaction, removals whose bytes are deleted after the commit (@dokebi/outbox), a sweep of unowned files after a grace, read rules per owner type, uploads, downloads and thumbnails behind them.",
771
1272
  "keywords": [
772
1273
  "dokebi",
773
1274
  "dokebi-plugin",
774
- "health",
775
- "readiness",
1275
+ "files",
1276
+ "uploads",
1277
+ "attachments",
1278
+ "storage"
1279
+ ],
1280
+ "maturity": "experimental",
1281
+ "core": "^0.4.2",
1282
+ "provides": [
1283
+ "files.records"
1284
+ ],
1285
+ "variants": [],
1286
+ "doesNot": [
1287
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
1288
+ "Keeps no bytes itself: they live in @dokebi/storage",
1289
+ "Keeps no deletion log of its own: removals answer the records for the audit log",
1290
+ "Does not create tables at start (the migration dokebi add writes)"
1291
+ ],
1292
+ "recipes": [],
1293
+ "requires": [
1294
+ {
1295
+ "id": "dokebi-db",
1296
+ "range": "^0.4.1",
1297
+ "package": "@dokebi/db",
1298
+ "automatic": true
1299
+ },
1300
+ {
1301
+ "id": "dokebi-storage",
1302
+ "range": "^0.1.0",
1303
+ "package": "@dokebi/storage",
1304
+ "automatic": true
1305
+ },
1306
+ {
1307
+ "id": "dokebi-outbox",
1308
+ "range": "^0.1.0",
1309
+ "package": "@dokebi/outbox",
1310
+ "automatic": true
1311
+ }
1312
+ ],
1313
+ "optional": [
1314
+ {
1315
+ "id": "dokebi-image",
1316
+ "range": "^0.1.0"
1317
+ }
1318
+ ],
1319
+ "optionKeys": [
1320
+ "connection",
1321
+ "list",
1322
+ "sweep",
1323
+ "unowned"
1324
+ ],
1325
+ "docs": {
1326
+ "ai": "AI.md",
1327
+ "readme": "README.md"
1328
+ },
1329
+ "install": "npx dokebi add @dokebi/files"
1330
+ },
1331
+ {
1332
+ "id": null,
1333
+ "package": "@dokebi/font-noto-kr",
1334
+ "version": "0.1.0",
1335
+ "kind": "library",
1336
+ "summary": "Noto Sans KR font files (Regular and Bold, SIL OFL 1.1) that @dokebi/pdf-writer embeds by default: Hangul, KS X 1001 Hanja, kana and Latin",
1337
+ "description": "Noto Sans CJK KR (Regular and Bold, OpenType CFF) for Dokebi PDF writing: Hangul, KS X 1001 Hanja, kana and Latin.",
1338
+ "keywords": [
1339
+ "dokebi",
1340
+ "font",
1341
+ "noto",
1342
+ "korean",
1343
+ "cjk",
1344
+ "pdf"
1345
+ ],
1346
+ "maturity": "experimental",
1347
+ "core": null,
1348
+ "provides": [
1349
+ "font.korean"
1350
+ ],
1351
+ "variants": [],
1352
+ "doesNot": [
1353
+ "Not a plugin you app.register: installed with @dokebi/pdf-writer",
1354
+ "No emoji and not every Chinese or Japanese character (add fallbackFonts of @dokebi/pdf-writer)"
1355
+ ],
1356
+ "recipes": [],
1357
+ "requires": [],
1358
+ "optional": [],
1359
+ "optionKeys": [],
1360
+ "docs": {
1361
+ "ai": "AI.md",
1362
+ "readme": "README.md"
1363
+ },
1364
+ "install": "npm install @dokebi/font-noto-kr"
1365
+ },
1366
+ {
1367
+ "id": "dokebi-geoip",
1368
+ "package": "@dokebi/geoip",
1369
+ "version": "0.1.0",
1370
+ "kind": "plugin",
1371
+ "summary": "Offline IP geolocation from operator-provided .mmdb files (MaxMind GeoLite2 or GeoIP2, DB-IP Lite): country, region, city, coordinates, time zone and ISP in memory, private and reserved ranges answered without a lookup, files reopened without a restart, a download command",
1372
+ "description": "Offline IP geolocation for Dokebi from operator-provided MaxMind DB (.mmdb) files (GeoLite2, GeoIP2, DB-IP Lite): country, region, city, location and ISP in memory, complete special-purpose address ranges, a bounded LRU cache, files reopened without a restart, and a download command.",
1373
+ "keywords": [
1374
+ "dokebi",
1375
+ "dokebi-plugin",
1376
+ "geoip",
1377
+ "geolocation",
1378
+ "maxmind",
1379
+ "mmdb",
1380
+ "geolite2"
1381
+ ],
1382
+ "maturity": "experimental",
1383
+ "core": "^0.4.2",
1384
+ "provides": [
1385
+ "geoip.lookup"
1386
+ ],
1387
+ "variants": [],
1388
+ "doesNot": [
1389
+ "Ships no database: the operator downloads one under its own license (GeoLite2 needs a free MaxMind account and its EULA; DB-IP Lite is CC BY 4.0)",
1390
+ "No network lookup service: the address never leaves the process",
1391
+ "No IP2Location BIN files and no CSV databases (MaxMind DB .mmdb only)",
1392
+ "No routes: the app decides who may see a location"
1393
+ ],
1394
+ "recipes": [],
1395
+ "requires": [],
1396
+ "optional": [
1397
+ {
1398
+ "id": "dokebi-health",
1399
+ "range": "^0.4.0"
1400
+ }
1401
+ ],
1402
+ "optionKeys": [
1403
+ "asn",
1404
+ "cache",
1405
+ "city",
1406
+ "download",
1407
+ "folder",
1408
+ "health",
1409
+ "language",
1410
+ "maxAgeDays",
1411
+ "maxFileBytes",
1412
+ "precision",
1413
+ "refresh"
1414
+ ],
1415
+ "docs": {
1416
+ "ai": "AI.md",
1417
+ "readme": "README.md"
1418
+ },
1419
+ "install": "npx dokebi add @dokebi/geoip"
1420
+ },
1421
+ {
1422
+ "id": "dokebi-health",
1423
+ "package": "@dokebi/health",
1424
+ "version": "0.4.1",
1425
+ "kind": "plugin",
1426
+ "summary": "Collects the live, ready and draining state of the app and the readiness checks of plugins (web serves the HTTP paths)",
1427
+ "description": "Liveness, readiness and draining state with plugin readiness checks for Dokebi.",
1428
+ "keywords": [
1429
+ "dokebi",
1430
+ "dokebi-plugin",
1431
+ "health",
1432
+ "readiness",
776
1433
  "liveness"
777
1434
  ],
778
1435
  "maturity": "preview",
@@ -783,9 +1440,13 @@
783
1440
  "variants": [],
784
1441
  "doesNot": [
785
1442
  "No periodic probes or automatic restarts",
786
- "No aggregation across the cluster"
1443
+ "No aggregation across the cluster (web's ready route asks every worker)"
1444
+ ],
1445
+ "recipes": [
1446
+ "api",
1447
+ "blog",
1448
+ "site"
787
1449
  ],
788
- "recipes": [],
789
1450
  "requires": [],
790
1451
  "optional": [],
791
1452
  "optionKeys": [
@@ -797,10 +1458,134 @@
797
1458
  },
798
1459
  "install": "npx dokebi add @dokebi/health"
799
1460
  },
1461
+ {
1462
+ "id": "dokebi-i18n",
1463
+ "package": "@dokebi/i18n",
1464
+ "version": "0.2.0",
1465
+ "kind": "plugin",
1466
+ "summary": "Compiled translations, locale negotiation, cardinal plurals and named number/date formats",
1467
+ "description": "Compiled translation catalogs, locale negotiation, cardinal plurals and named Intl formats.",
1468
+ "keywords": [
1469
+ "dokebi",
1470
+ "dokebi-plugin",
1471
+ "i18n",
1472
+ "locale"
1473
+ ],
1474
+ "maturity": "experimental",
1475
+ "core": "^0.4.0",
1476
+ "provides": [
1477
+ "i18n",
1478
+ "time.site-zone"
1479
+ ],
1480
+ "variants": [],
1481
+ "doesNot": [
1482
+ "No translation service; the Vue page runtime is @dokebi/template-inertia/i18n and HTTP integration is opt-in",
1483
+ "Plain text with named interpolation and cardinal plurals; not full ICU MessageFormat"
1484
+ ],
1485
+ "recipes": [],
1486
+ "requires": [],
1487
+ "optional": [
1488
+ {
1489
+ "id": "dokebi-web",
1490
+ "range": "^0.4.3"
1491
+ }
1492
+ ],
1493
+ "optionKeys": [
1494
+ "defaultLocale",
1495
+ "http",
1496
+ "maxAcceptLanguageLength",
1497
+ "maxAcceptLanguageRanges",
1498
+ "maxBranchLength",
1499
+ "maxCatalogBytes",
1500
+ "maxCatalogCharacters",
1501
+ "maxDefaultCharacters",
1502
+ "maxDefaultTemplates",
1503
+ "maxKeyLength",
1504
+ "maxLocales",
1505
+ "maxMessages",
1506
+ "maxNamedFormats",
1507
+ "maxOutputLength",
1508
+ "missing",
1509
+ "timeZone"
1510
+ ],
1511
+ "docs": {
1512
+ "ai": "AI.md",
1513
+ "readme": "README.md"
1514
+ },
1515
+ "install": "npx dokebi add @dokebi/i18n"
1516
+ },
1517
+ {
1518
+ "id": "dokebi-image",
1519
+ "package": "@dokebi/image",
1520
+ "version": "0.1.0",
1521
+ "kind": "plugin",
1522
+ "summary": "Server-side WebP thumbnails of stored png, jpeg, gif, webp and avif images, made once and deleted with their source",
1523
+ "description": "Bounded server-side WebP thumbnails of stored images, made once and deleted with their source.",
1524
+ "keywords": [
1525
+ "dokebi",
1526
+ "dokebi-plugin",
1527
+ "image",
1528
+ "thumbnail",
1529
+ "webp",
1530
+ "sharp"
1531
+ ],
1532
+ "maturity": "experimental",
1533
+ "core": "^0.4.2",
1534
+ "provides": [
1535
+ "image.thumbnail"
1536
+ ],
1537
+ "variants": [],
1538
+ "doesNot": [
1539
+ "No public routes, access policy, cropping, watermarks, animation or formats other than WebP output",
1540
+ "No SVG, HTML or other non-raster input (refused as UNSUPPORTED_IMAGE)"
1541
+ ],
1542
+ "recipes": [],
1543
+ "requires": [
1544
+ {
1545
+ "id": "dokebi-storage",
1546
+ "range": "^0.1.0",
1547
+ "package": "@dokebi/storage",
1548
+ "automatic": true
1549
+ }
1550
+ ],
1551
+ "optional": [
1552
+ {
1553
+ "id": "dokebi-lock",
1554
+ "range": "^0.4.0"
1555
+ }
1556
+ ],
1557
+ "optionKeys": [
1558
+ "busyRetryAfterMs",
1559
+ "effort",
1560
+ "lockBackend",
1561
+ "lockUnavailable",
1562
+ "lockWaitTimeoutMs",
1563
+ "logRepeatWindowMs",
1564
+ "maxConcurrent",
1565
+ "maxInputBytes",
1566
+ "maxInputPixels",
1567
+ "maxOutputBytes",
1568
+ "maxSideMax",
1569
+ "maxSideMin",
1570
+ "maxWaiting",
1571
+ "quality",
1572
+ "sharpConcurrency",
1573
+ "sweepIntervalMs",
1574
+ "sweepLimit",
1575
+ "timeoutMs",
1576
+ "timeoutRetryAfterMs",
1577
+ "waitTimeoutMs"
1578
+ ],
1579
+ "docs": {
1580
+ "ai": "AI.md",
1581
+ "readme": "README.md"
1582
+ },
1583
+ "install": "npx dokebi add @dokebi/image"
1584
+ },
800
1585
  {
801
1586
  "id": "dokebi-job",
802
1587
  "package": "@dokebi/job",
803
- "version": "0.4.0",
1588
+ "version": "0.4.3",
804
1589
  "kind": "plugin",
805
1590
  "summary": "NATS JetStream job queue (at least once, retries, DLQ)",
806
1591
  "description": "Bounded durable jobs over the Dokebi NATS provider: retries, delayed redelivery and a dead-letter stream.",
@@ -822,19 +1607,24 @@
822
1607
  "No exactly-once delivery (handlers must be idempotent)",
823
1608
  "No database"
824
1609
  ],
825
- "recipes": [],
1610
+ "recipes": [
1611
+ "api",
1612
+ "blog",
1613
+ "site"
1614
+ ],
826
1615
  "requires": [
827
1616
  {
828
1617
  "id": "dokebi-nats",
829
- "range": "^0.4.0"
830
- }
831
- ],
832
- "optional": [
1618
+ "range": "^0.4.1"
1619
+ },
833
1620
  {
834
1621
  "id": "dokebi-health",
835
- "range": "^0.4.0"
1622
+ "range": "^0.4.0",
1623
+ "package": "@dokebi/health",
1624
+ "automatic": true
836
1625
  }
837
1626
  ],
1627
+ "optional": [],
838
1628
  "optionKeys": [
839
1629
  "queues"
840
1630
  ],
@@ -847,7 +1637,7 @@
847
1637
  {
848
1638
  "id": "dokebi-lock",
849
1639
  "package": "@dokebi/lock",
850
- "version": "0.4.0",
1640
+ "version": "0.4.1",
851
1641
  "kind": "plugin",
852
1642
  "summary": "Named locks (local by default; redis leases through the @dokebi/lock-redis driver) with fencing tokens",
853
1643
  "description": "Named lock backends for Dokebi with fencing tokens: in-process or Redis leases.",
@@ -859,7 +1649,7 @@
859
1649
  "redis"
860
1650
  ],
861
1651
  "maturity": "preview",
862
- "core": "^0.4.0",
1652
+ "core": "^0.4.2",
863
1653
  "provides": [
864
1654
  "lock.local"
865
1655
  ],
@@ -876,7 +1666,8 @@
876
1666
  }
877
1667
  ],
878
1668
  "optionKeys": [
879
- "backends"
1669
+ "backends",
1670
+ "retryAfterMs"
880
1671
  ],
881
1672
  "docs": {
882
1673
  "ai": "AI.md",
@@ -887,7 +1678,7 @@
887
1678
  {
888
1679
  "id": null,
889
1680
  "package": "@dokebi/lock-redis",
890
- "version": "0.4.0",
1681
+ "version": "0.4.1",
891
1682
  "kind": "driver",
892
1683
  "summary": "Redis lease driver of @dokebi/lock (not a plugin: install it with npm install)",
893
1684
  "description": "Redis lease driver for @dokebi/lock (not a plugin; install with npm).",
@@ -919,116 +1710,388 @@
919
1710
  "install": "npm install @dokebi/lock-redis"
920
1711
  },
921
1712
  {
922
- "id": "dokebi-migrate",
923
- "package": "@dokebi/migrate",
924
- "version": "0.4.0",
1713
+ "id": "dokebi-logger",
1714
+ "package": "@dokebi/logger",
1715
+ "version": "0.1.0",
925
1716
  "kind": "plugin",
926
- "summary": "Migration and seed runner for dokebi-db connections (dokebi-migrate up in the deploy step)",
927
- "description": "Migrations and seeds for dokebi-db connections: history and lock for PostgreSQL, MySQL, MariaDB and MongoDB, run from the deployment.",
1717
+ "summary": "The app logger on pino: JSON to stdout, rotating files, pretty development output and network targets (NDJSON batches to Dokebi Studio, OTLP/HTTP JSON) at once, written in worker threads with bounded buffers, secrets redacted, levels per plugin, flushed on shutdown",
1718
+ "description": "Production logging for Dokebi apps on pino: stdout, rotating files, pretty output and HTTP/OTLP log shipping at once, written off the event loop, with redaction, per-plugin levels and flush on shutdown.",
928
1719
  "keywords": [
929
1720
  "dokebi",
930
1721
  "dokebi-plugin",
931
- "database",
932
- "migrations",
933
- "seeds"
934
- ],
935
- "maturity": "preview",
936
- "core": "^0.4.0",
1722
+ "logging",
1723
+ "pino",
1724
+ "log-rotation",
1725
+ "redaction",
1726
+ "otlp"
1727
+ ],
1728
+ "maturity": "experimental",
1729
+ "core": "^0.4.2",
937
1730
  "provides": [
938
- "db.migrate",
939
- "db.seed"
1731
+ "logging.network",
1732
+ "logging.redaction",
1733
+ "logging.rotation",
1734
+ "logging.targets"
940
1735
  ],
941
1736
  "variants": [],
942
1737
  "doesNot": [
943
- "No DDL when the app starts",
944
- "No tables generated from ORM models"
1738
+ "No log search, viewer or retention beyond the rotated files and what the receiver of a network target keeps",
1739
+ "No OTLP over gRPC or protobuf, no Loki or Elasticsearch format: network targets send NDJSON (http) or OTLP/HTTP JSON (otlp)"
945
1740
  ],
946
1741
  "recipes": [
947
1742
  "api",
948
- "blog"
1743
+ "blog",
1744
+ "site"
949
1745
  ],
950
- "requires": [
1746
+ "requires": [],
1747
+ "optional": [
951
1748
  {
952
- "id": "dokebi-db",
953
- "range": "^0.4.0",
954
- "package": "@dokebi/db",
955
- "automatic": true
1749
+ "id": "dokebi-health",
1750
+ "range": "^0.4.0"
956
1751
  }
957
1752
  ],
958
- "optional": [],
959
1753
  "optionKeys": [
960
- "connections",
961
- "directory",
962
- "lockTimeoutMs",
963
- "lockTtlMs",
964
- "onPending"
1754
+ "confirmIntervalMs",
1755
+ "dropReportIntervalMs",
1756
+ "fields",
1757
+ "flushTimeoutMs",
1758
+ "level",
1759
+ "maxLineBytes",
1760
+ "maxMessageLength",
1761
+ "plugins",
1762
+ "redact",
1763
+ "requests",
1764
+ "targets"
965
1765
  ],
966
1766
  "docs": {
967
1767
  "ai": "AI.md",
968
1768
  "readme": "README.md"
969
1769
  },
970
- "install": "npx dokebi add @dokebi/migrate"
1770
+ "install": "npx dokebi add @dokebi/logger"
971
1771
  },
972
1772
  {
973
- "id": "dokebi-module",
974
- "package": "@dokebi/module",
975
- "version": "0.4.0",
1773
+ "id": "dokebi-mail",
1774
+ "package": "@dokebi/mail",
1775
+ "version": "0.4.2",
976
1776
  "kind": "plugin",
977
- "summary": "Modules that hold one feature each (src/shared/modules/<module>); an app attaches them under a prefix",
978
- "description": "Feature modules for Dokebi apps: controllers, models, views and routes in one folder mounted under a prefix.",
1777
+ "summary": "Mail: EJS templates per locale, SMTP with required STARTTLS (nodemailer) and Amazon SES v2, queued sending through @dokebi/job with sealed links and tokens, a development inbox and test outbox",
1778
+ "description": "Mail for Dokebi apps: EJS templates per locale, SMTP (nodemailer, STARTTLS required) and Amazon SES v2, queued sending through @dokebi/job with sealed data, deterministic Message-IDs, a development inbox and test helpers.",
979
1779
  "keywords": [
980
1780
  "dokebi",
981
1781
  "dokebi-plugin",
982
- "web",
983
- "modules",
984
- "mvc"
1782
+ "mail",
1783
+ "email",
1784
+ "smtp"
985
1785
  ],
986
- "maturity": "preview",
987
- "core": "^0.4.0",
1786
+ "maturity": "experimental",
1787
+ "core": "^0.4.2",
988
1788
  "provides": [
989
- "web.modules"
1789
+ "mail"
990
1790
  ],
991
1791
  "variants": [],
992
1792
  "doesNot": [
993
- "Does not attach modules to apps by itself (modules in app.ts)"
1793
+ "No API providers other than SES; no bounce or complaint webhooks and no suppression list",
1794
+ "No password reset or e-mail verification flow (those come with dokebi-auth)",
1795
+ "Large attachments: only small inline ones (store files elsewhere and send a link)"
994
1796
  ],
995
1797
  "recipes": [
996
1798
  "api",
997
1799
  "blog"
998
1800
  ],
999
1801
  "requires": [],
1000
- "optional": [],
1001
- "optionKeys": [],
1802
+ "optional": [
1803
+ {
1804
+ "id": "dokebi-web",
1805
+ "range": "^0.4.0"
1806
+ },
1807
+ {
1808
+ "id": "dokebi-job",
1809
+ "range": "^0.4.2"
1810
+ },
1811
+ {
1812
+ "id": "dokebi-redis",
1813
+ "range": "^0.4.0"
1814
+ },
1815
+ {
1816
+ "id": "dokebi-i18n",
1817
+ "range": "^0.2.0"
1818
+ }
1819
+ ],
1820
+ "optionKeys": [
1821
+ "allowedFrom",
1822
+ "defaultLocale",
1823
+ "devInbox",
1824
+ "from",
1825
+ "layout",
1826
+ "logRepeatWindowMs",
1827
+ "maxAttachmentBytes",
1828
+ "maxAttachments",
1829
+ "maxDataDepth",
1830
+ "maxDataNodes",
1831
+ "maxHeaders",
1832
+ "maxRecipients",
1833
+ "maxTemplateFileBytes",
1834
+ "maxTemplates",
1835
+ "messageIdDomain",
1836
+ "queue",
1837
+ "redis",
1838
+ "replyTo",
1839
+ "returnPath",
1840
+ "routeIds",
1841
+ "secrets",
1842
+ "secretsEnv",
1843
+ "sentMarkerTtlMs",
1844
+ "siteUrl",
1845
+ "timeZone",
1846
+ "transport"
1847
+ ],
1002
1848
  "docs": {
1003
1849
  "ai": "AI.md",
1004
1850
  "readme": "README.md"
1005
1851
  },
1006
- "install": "npx dokebi add @dokebi/module"
1852
+ "install": "npx dokebi add @dokebi/mail"
1007
1853
  },
1008
1854
  {
1009
- "id": "dokebi-nats",
1010
- "package": "@dokebi/nats",
1011
- "version": "0.4.0",
1855
+ "id": "dokebi-mcp",
1856
+ "package": "@dokebi/mcp",
1857
+ "version": "0.1.0",
1012
1858
  "kind": "plugin",
1013
- "summary": "Named NATS connections (Core and JetStream publishing, durable consumers)",
1014
- "description": "Named NATS connections and bounded JetStream publication for Dokebi.",
1859
+ "summary": "MCP server on @dokebi/oauth: stateless Streamable HTTP at /mcp, tool classes with checked input schemas and scopes, a forced result envelope with paging, per-grant limits and coded tool errors",
1860
+ "description": "MCP server for @dokebi/web on @dokebi/oauth: stateless Streamable HTTP, tool classes with checked schemas, a forced result envelope, per-grant limits and coded tool errors.",
1015
1861
  "keywords": [
1016
1862
  "dokebi",
1017
1863
  "dokebi-plugin",
1018
- "nats",
1019
- "jetstream",
1020
- "messaging"
1864
+ "web",
1865
+ "mcp",
1866
+ "model-context-protocol",
1867
+ "ai",
1868
+ "oauth"
1021
1869
  ],
1022
- "maturity": "preview",
1023
- "core": "^0.4.0",
1870
+ "maturity": "experimental",
1871
+ "core": "^0.4.2",
1024
1872
  "provides": [
1025
- "messaging.nats"
1873
+ "mcp.server"
1026
1874
  ],
1027
1875
  "variants": [],
1028
1876
  "doesNot": [
1029
- "No raw SDK client and no stream management rights"
1877
+ "No stdio transport, no sessions and no server-sent events: one JSON answer per POST (GET and DELETE are 405)",
1878
+ "No sampling, elicitation or other requests from the server to the client; no list_changed notifications",
1879
+ "Does not authorize by itself: tokens come from @dokebi/oauth (one protected resource, one scope per tool)"
1030
1880
  ],
1031
1881
  "recipes": [],
1882
+ "requires": [
1883
+ {
1884
+ "id": "dokebi-web",
1885
+ "range": "^0.4.3"
1886
+ },
1887
+ {
1888
+ "id": "dokebi-oauth",
1889
+ "range": "^0.1.0"
1890
+ }
1891
+ ],
1892
+ "optional": [
1893
+ {
1894
+ "id": "dokebi-rate-limit",
1895
+ "range": "^0.1.1"
1896
+ }
1897
+ ],
1898
+ "optionKeys": [
1899
+ "allowedOrigins",
1900
+ "app",
1901
+ "bodyLimitBytes",
1902
+ "imageMaxBytes",
1903
+ "imageTotalMaxBytes",
1904
+ "instructions",
1905
+ "limits",
1906
+ "listTtlMs",
1907
+ "logRepeatWindowMs",
1908
+ "maxBatch",
1909
+ "maxImages",
1910
+ "name",
1911
+ "onCallMaxPending",
1912
+ "path",
1913
+ "resource",
1914
+ "resultMaxChars",
1915
+ "routeIds",
1916
+ "title",
1917
+ "toolTimeoutMs",
1918
+ "uriMaxChars",
1919
+ "version"
1920
+ ],
1921
+ "docs": {
1922
+ "ai": "AI.md",
1923
+ "readme": "README.md"
1924
+ },
1925
+ "install": "npx dokebi add @dokebi/mcp"
1926
+ },
1927
+ {
1928
+ "id": "dokebi-metrics",
1929
+ "package": "@dokebi/metrics",
1930
+ "version": "0.1.0",
1931
+ "kind": "plugin",
1932
+ "summary": "Bounded route latency quantiles, status counters and 5xx ratios in JSON or Prometheus text",
1933
+ "description": "Bounded per-route HTTP response metrics with JSON and Prometheus output for Dokebi.",
1934
+ "keywords": [
1935
+ "dokebi",
1936
+ "dokebi-plugin",
1937
+ "metrics",
1938
+ "prometheus",
1939
+ "http"
1940
+ ],
1941
+ "maturity": "experimental",
1942
+ "core": "^0.4.0",
1943
+ "provides": [
1944
+ "metrics.http"
1945
+ ],
1946
+ "variants": [],
1947
+ "doesNot": [
1948
+ "No automatic public endpoint, persistence or cross-process aggregation",
1949
+ "Quantiles cover the last sampleSize completed responses, not a time window"
1950
+ ],
1951
+ "recipes": [],
1952
+ "requires": [
1953
+ {
1954
+ "id": "dokebi-web",
1955
+ "range": "^0.4.3",
1956
+ "package": "@dokebi/web",
1957
+ "automatic": true
1958
+ }
1959
+ ],
1960
+ "optional": [],
1961
+ "optionKeys": [
1962
+ "cluster",
1963
+ "enabled",
1964
+ "maxRoutes",
1965
+ "maxSourceSamples",
1966
+ "sampleSize"
1967
+ ],
1968
+ "docs": {
1969
+ "ai": "AI.md",
1970
+ "readme": "README.md"
1971
+ },
1972
+ "install": "npx dokebi add @dokebi/metrics"
1973
+ },
1974
+ {
1975
+ "id": "dokebi-migrate",
1976
+ "package": "@dokebi/migrate",
1977
+ "version": "0.4.2",
1978
+ "kind": "plugin",
1979
+ "summary": "Migration and seed runner for dokebi-db connections (dokebi-migrate up in the deploy step)",
1980
+ "description": "Migrations and seeds for dokebi-db connections: history and lock for PostgreSQL, MySQL, MariaDB and MongoDB, run from the deployment.",
1981
+ "keywords": [
1982
+ "dokebi",
1983
+ "dokebi-plugin",
1984
+ "database",
1985
+ "migrations",
1986
+ "seeds"
1987
+ ],
1988
+ "maturity": "preview",
1989
+ "core": "^0.4.2",
1990
+ "provides": [
1991
+ "db.migrate",
1992
+ "db.seed"
1993
+ ],
1994
+ "variants": [],
1995
+ "doesNot": [
1996
+ "No DDL when the app starts",
1997
+ "No tables generated from ORM models"
1998
+ ],
1999
+ "recipes": [
2000
+ "api",
2001
+ "blog"
2002
+ ],
2003
+ "requires": [
2004
+ {
2005
+ "id": "dokebi-db",
2006
+ "range": "^0.4.0",
2007
+ "package": "@dokebi/db",
2008
+ "automatic": true
2009
+ }
2010
+ ],
2011
+ "optional": [],
2012
+ "optionKeys": [
2013
+ "connections",
2014
+ "directory",
2015
+ "lockRetries",
2016
+ "lockRetryDelayMs",
2017
+ "lockTimeoutMs",
2018
+ "lockTtlMs",
2019
+ "lockWaitTimeoutMs",
2020
+ "onChanged",
2021
+ "onPending",
2022
+ "queryTimeoutMs",
2023
+ "statementTimeoutMs"
2024
+ ],
2025
+ "docs": {
2026
+ "ai": "AI.md",
2027
+ "readme": "README.md"
2028
+ },
2029
+ "install": "npx dokebi add @dokebi/migrate"
2030
+ },
2031
+ {
2032
+ "id": "dokebi-module",
2033
+ "package": "@dokebi/module",
2034
+ "version": "0.4.1",
2035
+ "kind": "plugin",
2036
+ "summary": "Modules that hold one feature each (src/shared/modules/<module>); an app attaches them under a prefix",
2037
+ "description": "Feature modules for Dokebi apps: controllers, models, views and routes in one folder mounted under a prefix.",
2038
+ "keywords": [
2039
+ "dokebi",
2040
+ "dokebi-plugin",
2041
+ "web",
2042
+ "modules",
2043
+ "mvc"
2044
+ ],
2045
+ "maturity": "preview",
2046
+ "core": "^0.4.0",
2047
+ "provides": [
2048
+ "web.modules"
2049
+ ],
2050
+ "variants": [],
2051
+ "doesNot": [
2052
+ "Does not attach modules to apps by itself (modules in app.ts)"
2053
+ ],
2054
+ "recipes": [
2055
+ "api",
2056
+ "blog"
2057
+ ],
2058
+ "requires": [],
2059
+ "optional": [],
2060
+ "optionKeys": [],
2061
+ "docs": {
2062
+ "ai": "AI.md",
2063
+ "readme": "README.md"
2064
+ },
2065
+ "install": "npx dokebi add @dokebi/module"
2066
+ },
2067
+ {
2068
+ "id": "dokebi-nats",
2069
+ "package": "@dokebi/nats",
2070
+ "version": "0.4.1",
2071
+ "kind": "plugin",
2072
+ "summary": "Named NATS connections (Core and JetStream publishing, durable consumers)",
2073
+ "description": "Named NATS connections and bounded JetStream publication for Dokebi.",
2074
+ "keywords": [
2075
+ "dokebi",
2076
+ "dokebi-plugin",
2077
+ "nats",
2078
+ "jetstream",
2079
+ "messaging"
2080
+ ],
2081
+ "maturity": "preview",
2082
+ "core": "^0.4.2",
2083
+ "provides": [
2084
+ "messaging.nats"
2085
+ ],
2086
+ "variants": [],
2087
+ "doesNot": [
2088
+ "No raw SDK client and no stream management rights"
2089
+ ],
2090
+ "recipes": [
2091
+ "api",
2092
+ "blog",
2093
+ "site"
2094
+ ],
1032
2095
  "requires": [],
1033
2096
  "optional": [
1034
2097
  {
@@ -1045,10 +2108,414 @@
1045
2108
  },
1046
2109
  "install": "npx dokebi add @dokebi/nats"
1047
2110
  },
2111
+ {
2112
+ "id": "dokebi-oauth",
2113
+ "package": "@dokebi/oauth",
2114
+ "version": "0.1.0",
2115
+ "kind": "plugin",
2116
+ "summary": "OAuth 2.1 authorization server on @dokebi/auth tokens: authorization code with PKCE S256, refresh rotation and reuse detection, revocation, client ID metadata documents, protected resource metadata and protected routes",
2117
+ "description": "OAuth 2.1 authorization server for @dokebi/web on @dokebi/auth tokens: authorization code with PKCE, refresh rotation, revocation, client ID metadata documents, protected resource metadata and route protection.",
2118
+ "keywords": [
2119
+ "dokebi",
2120
+ "dokebi-plugin",
2121
+ "web",
2122
+ "oauth",
2123
+ "oauth2",
2124
+ "authorization-server",
2125
+ "pkce",
2126
+ "mcp"
2127
+ ],
2128
+ "maturity": "experimental",
2129
+ "core": "^0.4.2",
2130
+ "provides": [
2131
+ "oauth.server"
2132
+ ],
2133
+ "variants": [],
2134
+ "doesNot": [
2135
+ "No OpenID Connect: no id_token, userinfo or openid-configuration",
2136
+ "No implicit, password, client_credentials or device grants, no introspection, no JWT access tokens, no private_key_jwt",
2137
+ "Does not draw the consent screen: the application renders it (renderConsent in src/infra/oauth/extension.ts)",
2138
+ "Dynamic client registration is closed unless registration: open (public clients only)"
2139
+ ],
2140
+ "recipes": [],
2141
+ "requires": [
2142
+ {
2143
+ "id": "dokebi-web",
2144
+ "range": "^0.4.3"
2145
+ },
2146
+ {
2147
+ "id": "dokebi-auth",
2148
+ "range": "^0.4.1"
2149
+ },
2150
+ {
2151
+ "id": "dokebi-session",
2152
+ "range": "^0.4.4"
2153
+ }
2154
+ ],
2155
+ "optional": [
2156
+ {
2157
+ "id": "dokebi-csrf",
2158
+ "range": "^0.4.0"
2159
+ },
2160
+ {
2161
+ "id": "dokebi-db",
2162
+ "range": "^0.4.0"
2163
+ },
2164
+ {
2165
+ "id": "dokebi-redis",
2166
+ "range": "^0.4.0"
2167
+ },
2168
+ {
2169
+ "id": "dokebi-rate-limit",
2170
+ "range": "^0.1.1"
2171
+ }
2172
+ ],
2173
+ "optionKeys": [
2174
+ "accessMs",
2175
+ "app",
2176
+ "cimd",
2177
+ "clientUnusedMs",
2178
+ "clients",
2179
+ "codeGraceMs",
2180
+ "codeMs",
2181
+ "grant",
2182
+ "grantCacheMs",
2183
+ "grantTouchMs",
2184
+ "issuer",
2185
+ "live",
2186
+ "logRepeatWindowMs",
2187
+ "maxBodyBytes",
2188
+ "maxCachedGrants",
2189
+ "maxPending",
2190
+ "maxTrackedAddresses",
2191
+ "paths",
2192
+ "pendingMs",
2193
+ "rateLimit",
2194
+ "realm",
2195
+ "refreshMs",
2196
+ "registration",
2197
+ "registrationLimit",
2198
+ "registrationWindowMs",
2199
+ "resources",
2200
+ "routeIds",
2201
+ "store",
2202
+ "storeFullRetryAfterMs",
2203
+ "storeRetryAfterMs"
2204
+ ],
2205
+ "docs": {
2206
+ "ai": "AI.md",
2207
+ "readme": "README.md"
2208
+ },
2209
+ "install": "npx dokebi add @dokebi/oauth"
2210
+ },
2211
+ {
2212
+ "id": "dokebi-outbox",
2213
+ "package": "@dokebi/outbox",
2214
+ "version": "0.1.0",
2215
+ "kind": "plugin",
2216
+ "summary": "Durable background work in your database: enqueue in the business transaction, fair batch claims across owners, leases, backoff, per-destination breakers that wait out outages, dead items with bulk resend, retention, arrival and drain numbers",
2217
+ "description": "Durable database-backed background work for Dokebi: enqueue in the business transaction, fair batch claims across owners, leases, retries with backoff, per-destination breakers, dead items with bulk resend, retention and arrival/drain numbers.",
2218
+ "keywords": [
2219
+ "dokebi",
2220
+ "dokebi-plugin",
2221
+ "outbox",
2222
+ "background",
2223
+ "queue",
2224
+ "database",
2225
+ "retry"
2226
+ ],
2227
+ "maturity": "experimental",
2228
+ "core": "^0.4.2",
2229
+ "provides": [
2230
+ "background.outbox"
2231
+ ],
2232
+ "variants": [],
2233
+ "doesNot": [
2234
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
2235
+ "Not exactly once: handlers run at least once and must be idempotent",
2236
+ "No cron: time-of-day schedules stay with @dokebi/scheduler; delayed items use notBefore",
2237
+ "Does not create tables at start (the migration dokebi add writes)"
2238
+ ],
2239
+ "recipes": [],
2240
+ "requires": [
2241
+ {
2242
+ "id": "dokebi-db",
2243
+ "range": "^0.4.1",
2244
+ "package": "@dokebi/db",
2245
+ "automatic": true
2246
+ }
2247
+ ],
2248
+ "optional": [
2249
+ {
2250
+ "id": "dokebi-health",
2251
+ "range": "^0.4.0"
2252
+ }
2253
+ ],
2254
+ "optionKeys": [
2255
+ "abandonAfterMs",
2256
+ "breaker",
2257
+ "connection",
2258
+ "handlerPool",
2259
+ "handlerTimeoutMs",
2260
+ "health",
2261
+ "leaseGraceMs",
2262
+ "maxContinuations",
2263
+ "maxNotBeforeMs",
2264
+ "maxPageSize",
2265
+ "maxPayloadBytes",
2266
+ "maxProgressBytes",
2267
+ "maxResend",
2268
+ "pool",
2269
+ "retention",
2270
+ "retry",
2271
+ "runner",
2272
+ "stats",
2273
+ "types"
2274
+ ],
2275
+ "docs": {
2276
+ "ai": "AI.md",
2277
+ "readme": "README.md"
2278
+ },
2279
+ "install": "npx dokebi add @dokebi/outbox"
2280
+ },
2281
+ {
2282
+ "id": "dokebi-pdf",
2283
+ "package": "@dokebi/pdf",
2284
+ "version": "0.1.0",
2285
+ "kind": "plugin",
2286
+ "summary": "Text of stored PDF files as page parts and a first-page WebP thumbnail, in worker threads with bounded bytes, pages, characters, time and memory, made once and deleted with their source",
2287
+ "description": "Bounded text extraction and first-page WebP thumbnails of stored PDF files, in worker threads, made once and deleted with their source.",
2288
+ "keywords": [
2289
+ "dokebi",
2290
+ "dokebi-plugin",
2291
+ "pdf",
2292
+ "text-extraction",
2293
+ "thumbnail",
2294
+ "webp"
2295
+ ],
2296
+ "maturity": "experimental",
2297
+ "core": "^0.4.2",
2298
+ "provides": [
2299
+ "pdf.text",
2300
+ "pdf.thumbnail"
2301
+ ],
2302
+ "variants": [],
2303
+ "doesNot": [
2304
+ "No OCR: a scanned PDF answers hasText false",
2305
+ "No password entry, forms, scripts, attachments, XFA or links: an encrypted PDF is PDF_ENCRYPTED",
2306
+ "No public routes or access policy; no PDF editing (writing PDF files is @dokebi/pdf-writer)"
2307
+ ],
2308
+ "recipes": [],
2309
+ "requires": [
2310
+ {
2311
+ "id": "dokebi-storage",
2312
+ "range": "^0.1.0",
2313
+ "package": "@dokebi/storage",
2314
+ "automatic": true
2315
+ }
2316
+ ],
2317
+ "optional": [],
2318
+ "optionKeys": [
2319
+ "busyRetryAfterMs",
2320
+ "maxConcurrent",
2321
+ "maxImagePixels",
2322
+ "maxInputBytes",
2323
+ "maxOutputBytes",
2324
+ "maxPageChars",
2325
+ "maxPages",
2326
+ "maxPendingBytes",
2327
+ "maxSideMax",
2328
+ "maxSideMin",
2329
+ "maxTotalChars",
2330
+ "maxWaiting",
2331
+ "quality",
2332
+ "sweepLimit",
2333
+ "timeoutMs",
2334
+ "timeoutRetryAfterMs",
2335
+ "waitTimeoutMs",
2336
+ "workerIdleMs",
2337
+ "workerMemoryMb"
2338
+ ],
2339
+ "docs": {
2340
+ "ai": "AI.md",
2341
+ "readme": "README.md"
2342
+ },
2343
+ "install": "npx dokebi add @dokebi/pdf"
2344
+ },
2345
+ {
2346
+ "id": "dokebi-pdf-writer",
2347
+ "package": "@dokebi/pdf-writer",
2348
+ "version": "0.1.0",
2349
+ "kind": "plugin",
2350
+ "summary": "PDF files written from markdown or blocks (headings, paragraphs, lists, tables, code, quotes, stored JPEG/PNG images) with embedded Korean fonts and font fallback, in worker threads with bounded pages, bytes, time and memory, stored in dokebi-storage",
2351
+ "description": "PDF files written from markdown or blocks with embedded Korean fonts and stored images, in worker threads, stored in dokebi-storage.",
2352
+ "keywords": [
2353
+ "dokebi",
2354
+ "dokebi-plugin",
2355
+ "pdf",
2356
+ "pdf-generation",
2357
+ "markdown",
2358
+ "korean"
2359
+ ],
2360
+ "maturity": "experimental",
2361
+ "core": "^0.4.2",
2362
+ "provides": [
2363
+ "pdf.generate"
2364
+ ],
2365
+ "variants": [],
2366
+ "doesNot": [
2367
+ "No HTML or CSS rendering: markdown HTML is printed as text, scripts never run",
2368
+ "No WebP, GIF or AVIF images (they are skipped and reported); no remote images or fonts",
2369
+ "No forms, JavaScript, attachments, encryption or signatures in the written PDF; no public routes or access policy"
2370
+ ],
2371
+ "recipes": [],
2372
+ "requires": [
2373
+ {
2374
+ "id": "dokebi-storage",
2375
+ "range": "^0.1.0",
2376
+ "package": "@dokebi/storage",
2377
+ "automatic": true
2378
+ }
2379
+ ],
2380
+ "optional": [
2381
+ {
2382
+ "id": "dokebi-image",
2383
+ "range": "^0.1.0"
2384
+ }
2385
+ ],
2386
+ "optionKeys": [
2387
+ "boldFont",
2388
+ "busyRetryAfterMs",
2389
+ "fallbackFonts",
2390
+ "font",
2391
+ "fontSize",
2392
+ "marginPt",
2393
+ "maxBlocks",
2394
+ "maxConcurrent",
2395
+ "maxImageBytes",
2396
+ "maxImagePixels",
2397
+ "maxImages",
2398
+ "maxOutputBytes",
2399
+ "maxPages",
2400
+ "maxPendingBytes",
2401
+ "maxSourceChars",
2402
+ "maxWaiting",
2403
+ "pageSize",
2404
+ "svgMaxSide",
2405
+ "timeoutMs",
2406
+ "timeoutRetryAfterMs",
2407
+ "waitTimeoutMs",
2408
+ "workerIdleMs",
2409
+ "workerMemoryMb"
2410
+ ],
2411
+ "docs": {
2412
+ "ai": "AI.md",
2413
+ "readme": "README.md"
2414
+ },
2415
+ "install": "npx dokebi add @dokebi/pdf-writer"
2416
+ },
2417
+ {
2418
+ "id": "dokebi-permissions",
2419
+ "package": "@dokebi/permissions",
2420
+ "version": "0.1.0",
2421
+ "kind": "plugin",
2422
+ "summary": "Roles, grades and scoped permissions: keys declared in code, grants saved in your database in the caller's transaction, can(actor, key, target) from memory over the holding tree (all, subtree, direct, own), list conditions as SQL, every worker refreshed by a revision; plugs into @dokebi/auth's authorize",
2423
+ "description": "Roles, grades and scoped permissions for Dokebi: permissions declared in code, grants saved in your database in the caller's transaction, checks from memory over the holding tree (all, subtree, direct, own), list conditions as SQL, every worker refreshed by a revision.",
2424
+ "keywords": [
2425
+ "dokebi",
2426
+ "dokebi-plugin",
2427
+ "permissions",
2428
+ "authorization",
2429
+ "roles",
2430
+ "rbac"
2431
+ ],
2432
+ "maturity": "experimental",
2433
+ "core": "^0.4.2",
2434
+ "provides": [
2435
+ "auth.permissions"
2436
+ ],
2437
+ "variants": [],
2438
+ "doesNot": [
2439
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
2440
+ "Does not own your roles, grades or members: it knows their keys and the tree you pass (id, parentId, path)",
2441
+ "Does not decide business rules such as which member types a grade may create",
2442
+ "Does not create tables at start (the migration dokebi add writes)"
2443
+ ],
2444
+ "recipes": [],
2445
+ "requires": [
2446
+ {
2447
+ "id": "dokebi-db",
2448
+ "range": "^0.4.1",
2449
+ "package": "@dokebi/db",
2450
+ "automatic": true
2451
+ }
2452
+ ],
2453
+ "optional": [],
2454
+ "optionKeys": [
2455
+ "connection",
2456
+ "refreshMs",
2457
+ "superRoles"
2458
+ ],
2459
+ "docs": {
2460
+ "ai": "AI.md",
2461
+ "readme": "README.md"
2462
+ },
2463
+ "install": "npx dokebi add @dokebi/permissions"
2464
+ },
2465
+ {
2466
+ "id": "dokebi-rate-limit",
2467
+ "package": "@dokebi/rate-limit",
2468
+ "version": "0.1.1",
2469
+ "kind": "plugin",
2470
+ "summary": "Bounded token buckets with local counters or shared atomic Redis limits",
2471
+ "description": "Bounded token-bucket limits with synchronous local and atomic shared Redis stores.",
2472
+ "keywords": [
2473
+ "dokebi",
2474
+ "dokebi-plugin",
2475
+ "rate-limit"
2476
+ ],
2477
+ "maturity": "experimental",
2478
+ "core": "^0.4.2",
2479
+ "provides": [
2480
+ "rate-limit"
2481
+ ],
2482
+ "variants": [],
2483
+ "doesNot": [
2484
+ "No automatic routes, trusted proxy configuration, user identification or distributed local state",
2485
+ "No automatic retries or fail-open fallback on storage failure"
2486
+ ],
2487
+ "recipes": [],
2488
+ "requires": [],
2489
+ "optional": [
2490
+ {
2491
+ "id": "dokebi-redis",
2492
+ "range": "^0.4.1"
2493
+ }
2494
+ ],
2495
+ "optionKeys": [
2496
+ "connection",
2497
+ "driver",
2498
+ "identity",
2499
+ "logRepeatWindowMs",
2500
+ "maxKeyLength",
2501
+ "maxPending",
2502
+ "maxPolicyRevisions",
2503
+ "namespace",
2504
+ "policies",
2505
+ "policyOverlapWarnMs",
2506
+ "policyRevisionTtlMs",
2507
+ "retryAfterMs"
2508
+ ],
2509
+ "docs": {
2510
+ "ai": "AI.md",
2511
+ "readme": "README.md"
2512
+ },
2513
+ "install": "npx dokebi add @dokebi/rate-limit"
2514
+ },
1048
2515
  {
1049
2516
  "id": "dokebi-redis",
1050
2517
  "package": "@dokebi/redis",
1051
- "version": "0.4.0",
2518
+ "version": "0.4.1",
1052
2519
  "kind": "plugin",
1053
2520
  "summary": "Named standalone Redis connections and leader election (coordination)",
1054
2521
  "description": "Named, lifecycle-owned Redis connections for Dokebi with leader election and coordination.",
@@ -1059,7 +2526,7 @@
1059
2526
  "coordination"
1060
2527
  ],
1061
2528
  "maturity": "preview",
1062
- "core": "^0.4.0",
2529
+ "core": "^0.4.2",
1063
2530
  "provides": [
1064
2531
  "redis.connection",
1065
2532
  "redis.coordination"
@@ -1078,18 +2545,110 @@
1078
2545
  }
1079
2546
  ],
1080
2547
  "optionKeys": [
1081
- "connections"
2548
+ "connections"
2549
+ ],
2550
+ "docs": {
2551
+ "ai": "AI.md",
2552
+ "readme": "README.md"
2553
+ },
2554
+ "install": "npx dokebi add @dokebi/redis"
2555
+ },
2556
+ {
2557
+ "id": null,
2558
+ "package": "@dokebi/rich-text",
2559
+ "version": "0.1.0",
2560
+ "kind": "library",
2561
+ "summary": "Editor documents (Tiptap or ProseMirror JSON) checked whole against a declared schema of nodes, marks, links and image sources, with coded 422 errors, then safe HTML, plain text and excerpts",
2562
+ "description": "Rich text documents for Dokebi: editor JSON (Tiptap, ProseMirror) checked whole against a declared schema with coded 422 errors, then safe HTML, plain text and excerpts.",
2563
+ "keywords": [
2564
+ "dokebi",
2565
+ "rich-text",
2566
+ "tiptap",
2567
+ "prosemirror",
2568
+ "sanitize",
2569
+ "html"
2570
+ ],
2571
+ "maturity": "experimental",
2572
+ "core": "^0.4.2",
2573
+ "provides": [
2574
+ "content.rich-text"
2575
+ ],
2576
+ "variants": [],
2577
+ "doesNot": [
2578
+ "Not a plugin you app.register: npm install it and define a schema in your code",
2579
+ "No editor (use Tiptap in the browser) and no custom nodes with your own HTML",
2580
+ "Does not clean arbitrary HTML: it writes HTML only from checked documents"
2581
+ ],
2582
+ "recipes": [],
2583
+ "requires": [],
2584
+ "optional": [],
2585
+ "optionKeys": [],
2586
+ "docs": {
2587
+ "ai": "AI.md",
2588
+ "readme": "README.md"
2589
+ },
2590
+ "install": "npm install @dokebi/rich-text"
2591
+ },
2592
+ {
2593
+ "id": "dokebi-rollups",
2594
+ "package": "@dokebi/rollups",
2595
+ "version": "0.1.0",
2596
+ "kind": "plugin",
2597
+ "summary": "Report roll-ups by @dokebi/db AI.md 6.9: facts recorded as small rows in the caller's transaction (no hot row), folded in parallel by outbox lanes into UTC hour rows and day and month rows per whole-hour time zone (rebuilt from the hours when a zone is added), bounded reads with an as-of time, running totals and a report budget (429, 503)",
2598
+ "description": "Report roll-ups for Dokebi by @dokebi/db AI.md 6.9: facts recorded as small rows in the caller's transaction, folded by outbox lanes into UTC hour rows and day and month rows per time zone, bounded reads with an as-of time and a report budget.",
2599
+ "keywords": [
2600
+ "dokebi",
2601
+ "dokebi-plugin",
2602
+ "reports",
2603
+ "statistics",
2604
+ "rollups",
2605
+ "aggregates"
2606
+ ],
2607
+ "maturity": "experimental",
2608
+ "core": "^0.4.2",
2609
+ "provides": [
2610
+ "reports.rollups"
2611
+ ],
2612
+ "variants": [],
2613
+ "doesNot": [
2614
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
2615
+ "No rankings (top N per bucket) and no distinct counts (active members): sums only",
2616
+ "No time zones with half-hour offsets: day rows are cut from UTC hour rows",
2617
+ "Does not create tables at start (the migration dokebi add writes)"
2618
+ ],
2619
+ "recipes": [],
2620
+ "requires": [
2621
+ {
2622
+ "id": "dokebi-db",
2623
+ "range": "^0.4.1",
2624
+ "package": "@dokebi/db",
2625
+ "automatic": true
2626
+ },
2627
+ {
2628
+ "id": "dokebi-outbox",
2629
+ "range": "^0.1.0",
2630
+ "package": "@dokebi/outbox",
2631
+ "automatic": true
2632
+ }
2633
+ ],
2634
+ "optional": [],
2635
+ "optionKeys": [
2636
+ "connection",
2637
+ "fold",
2638
+ "rebuild",
2639
+ "reports",
2640
+ "zones"
1082
2641
  ],
1083
2642
  "docs": {
1084
2643
  "ai": "AI.md",
1085
2644
  "readme": "README.md"
1086
2645
  },
1087
- "install": "npx dokebi add @dokebi/redis"
2646
+ "install": "npx dokebi add @dokebi/rollups"
1088
2647
  },
1089
2648
  {
1090
2649
  "id": "dokebi-router",
1091
2650
  "package": "@dokebi/router",
1092
- "version": "0.4.0",
2651
+ "version": "0.4.1",
1093
2652
  "kind": "plugin",
1094
2653
  "summary": "Adds routes in a prefix scope on top of dokebi-web (mount)",
1095
2654
  "description": "Prefix-mounted Fastify route scopes for @dokebi/web.",
@@ -1124,10 +2683,58 @@
1124
2683
  },
1125
2684
  "install": "npx dokebi add @dokebi/router"
1126
2685
  },
2686
+ {
2687
+ "id": "dokebi-runtime-settings",
2688
+ "package": "@dokebi/runtime-settings",
2689
+ "version": "0.1.0",
2690
+ "kind": "plugin",
2691
+ "summary": "Settings operators change while the app runs: definitions in code (a group tree, text, number, numbers per choice, boolean, select, multiselect, json and file with rules, choices from your data), common and personal values saved in your database in the caller's transaction (the changes answered for the audit log), common values read from memory, every worker refreshed by a revision",
2692
+ "description": "Settings operators change while the app runs, for Dokebi: definitions in code (a group tree, nine types with rules, choices from your data), common and personal values in your database saved in the caller's transaction, common values read from memory, every worker refreshed by a revision.",
2693
+ "keywords": [
2694
+ "dokebi",
2695
+ "dokebi-plugin",
2696
+ "settings",
2697
+ "configuration",
2698
+ "admin",
2699
+ "database"
2700
+ ],
2701
+ "maturity": "experimental",
2702
+ "core": "^0.4.2",
2703
+ "provides": [
2704
+ "settings.runtime"
2705
+ ],
2706
+ "variants": [],
2707
+ "doesNot": [
2708
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
2709
+ "Keeps no history of its own: write the changes it answers with @dokebi/audit in the same transaction",
2710
+ "Does not store files: a file setting holds a stored file's key, name, size and type",
2711
+ "Settings that change only with a deploy belong in dokebi.yaml (npx dokebi settings add)",
2712
+ "Does not create tables at start (the migration dokebi add writes)"
2713
+ ],
2714
+ "recipes": [],
2715
+ "requires": [
2716
+ {
2717
+ "id": "dokebi-db",
2718
+ "range": "^0.4.1",
2719
+ "package": "@dokebi/db",
2720
+ "automatic": true
2721
+ }
2722
+ ],
2723
+ "optional": [],
2724
+ "optionKeys": [
2725
+ "connection",
2726
+ "refreshMs"
2727
+ ],
2728
+ "docs": {
2729
+ "ai": "AI.md",
2730
+ "readme": "README.md"
2731
+ },
2732
+ "install": "npx dokebi add @dokebi/runtime-settings"
2733
+ },
1127
2734
  {
1128
2735
  "id": "dokebi-scheduler",
1129
2736
  "package": "@dokebi/scheduler",
1130
- "version": "0.4.0",
2737
+ "version": "0.4.2",
1131
2738
  "kind": "plugin",
1132
2739
  "summary": "Submits fixed-interval and cron schedules as @dokebi/job jobs (one leader through Redis)",
1133
2740
  "description": "Interval and cron schedules (with time zones) for Dokebi that submit jobs, with one leader over Redis.",
@@ -1163,6 +2770,8 @@
1163
2770
  "optionKeys": [
1164
2771
  "allowInCluster",
1165
2772
  "coordination",
2773
+ "logRepeatWindowMs",
2774
+ "retryAfterMs",
1166
2775
  "schedules",
1167
2776
  "shutdownTimeoutMs"
1168
2777
  ],
@@ -1175,7 +2784,7 @@
1175
2784
  {
1176
2785
  "id": "dokebi-seal",
1177
2786
  "package": "@dokebi/seal",
1178
- "version": "0.4.0",
2787
+ "version": "0.4.2",
1179
2788
  "kind": "plugin",
1180
2789
  "summary": "Seals WebSocket frames and HTTP bodies (obfuscation, replay protection) with a browser client (WASM)",
1181
2790
  "description": "Payload sealing (obfuscation, replay defence, in-transit encryption) for seal hosts: @dokebi/websocket frames and @dokebi/web HTTP bodies, with browser socket, fetch and Inertia clients and their WASM loader",
@@ -1187,7 +2796,7 @@
1187
2796
  "obfuscation"
1188
2797
  ],
1189
2798
  "maturity": "preview",
1190
- "core": "^0.4.0",
2799
+ "core": "^0.4.2",
1191
2800
  "provides": [
1192
2801
  "security.seal"
1193
2802
  ],
@@ -1206,19 +2815,80 @@
1206
2815
  {
1207
2816
  "id": "dokebi-websocket-hub",
1208
2817
  "range": "^0.4.0"
2818
+ },
2819
+ {
2820
+ "id": "dokebi-redis",
2821
+ "range": "^0.4.0"
1209
2822
  }
1210
2823
  ],
1211
- "optionKeys": [],
2824
+ "optionKeys": [
2825
+ "logRepeatWindowMs",
2826
+ "nonces",
2827
+ "routeIds"
2828
+ ],
1212
2829
  "docs": {
1213
2830
  "ai": "AI.md",
1214
2831
  "readme": "README.md"
1215
2832
  },
1216
2833
  "install": "npx dokebi add @dokebi/seal"
1217
2834
  },
2835
+ {
2836
+ "id": "dokebi-search",
2837
+ "package": "@dokebi/search",
2838
+ "version": "0.1.0",
2839
+ "kind": "plugin",
2840
+ "summary": "Search in your lists that stays bounded on a growing table: a term table in display order written in the text's transaction, Korean word prefixes, the rarest word leads, capped requests and searches with a signed cursor; answers item ids",
2841
+ "description": "Search in an application's lists for Dokebi by @dokebi/db AI.md 6.10: a term table in display order written in the text's transaction, Korean word prefixes, the rarest word leads, bounded requests and searches with a signed cursor.",
2842
+ "keywords": [
2843
+ "dokebi",
2844
+ "dokebi-plugin",
2845
+ "search",
2846
+ "full-text",
2847
+ "database"
2848
+ ],
2849
+ "maturity": "experimental",
2850
+ "core": "^0.4.2",
2851
+ "provides": [
2852
+ "search"
2853
+ ],
2854
+ "variants": [],
2855
+ "doesNot": [
2856
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
2857
+ "No relevance ranking: results come in the index's display order",
2858
+ "Does not read or authorize your rows: it answers item ids, and hidden items are removed from the index by your code",
2859
+ "No typo tolerance, synonyms or stemming beyond word prefixes",
2860
+ "Does not create tables at start (the migration dokebi add writes)"
2861
+ ],
2862
+ "recipes": [],
2863
+ "requires": [
2864
+ {
2865
+ "id": "dokebi-db",
2866
+ "range": "^0.4.1",
2867
+ "package": "@dokebi/db",
2868
+ "automatic": true
2869
+ }
2870
+ ],
2871
+ "optional": [],
2872
+ "optionKeys": [
2873
+ "connection",
2874
+ "counts",
2875
+ "examineLimit",
2876
+ "list",
2877
+ "pool",
2878
+ "searchLimit",
2879
+ "secrets",
2880
+ "secretsEnv"
2881
+ ],
2882
+ "docs": {
2883
+ "ai": "AI.md",
2884
+ "readme": "README.md"
2885
+ },
2886
+ "install": "npx dokebi add @dokebi/search"
2887
+ },
1218
2888
  {
1219
2889
  "id": "dokebi-session",
1220
2890
  "package": "@dokebi/session",
1221
- "version": "0.4.0",
2891
+ "version": "0.4.4",
1222
2892
  "kind": "plugin",
1223
2893
  "summary": "Server-side sessions for @dokebi/web (signed cookie, memory or redis store, flash)",
1224
2894
  "description": "Server-side sessions for @dokebi/web: signed cookie, memory or Redis store, flash.",
@@ -1230,7 +2900,7 @@
1230
2900
  "cookie"
1231
2901
  ],
1232
2902
  "maturity": "preview",
1233
- "core": "^0.4.0",
2903
+ "core": "^0.4.2",
1234
2904
  "provides": [
1235
2905
  "web.session"
1236
2906
  ],
@@ -1240,12 +2910,13 @@
1240
2910
  "Does not issue CSRF tokens"
1241
2911
  ],
1242
2912
  "recipes": [
1243
- "blog"
2913
+ "blog",
2914
+ "site"
1244
2915
  ],
1245
2916
  "requires": [
1246
2917
  {
1247
2918
  "id": "dokebi-web",
1248
- "range": "^0.4.0"
2919
+ "range": "^0.4.3"
1249
2920
  }
1250
2921
  ],
1251
2922
  "optional": [
@@ -1256,18 +2927,28 @@
1256
2927
  {
1257
2928
  "id": "dokebi-health",
1258
2929
  "range": "^0.4.0"
2930
+ },
2931
+ {
2932
+ "id": "dokebi-geoip",
2933
+ "range": "^0.1.0"
1259
2934
  }
1260
2935
  ],
1261
2936
  "optionKeys": [
1262
2937
  "absoluteMs",
2938
+ "apps",
1263
2939
  "cookie",
2940
+ "devices",
1264
2941
  "exclude",
2942
+ "logRepeatWindowMs",
1265
2943
  "maxBytes",
2944
+ "maxMergeAttempts",
2945
+ "maxOnce",
1266
2946
  "refreshBelow",
1267
2947
  "rolling",
1268
2948
  "secrets",
1269
2949
  "secretsEnv",
1270
2950
  "store",
2951
+ "storeProbeIntervalMs",
1271
2952
  "ttlMs"
1272
2953
  ],
1273
2954
  "docs": {
@@ -1279,7 +2960,7 @@
1279
2960
  {
1280
2961
  "id": "dokebi-sse",
1281
2962
  "package": "@dokebi/sse",
1282
- "version": "0.4.0",
2963
+ "version": "0.4.2",
1283
2964
  "kind": "plugin",
1284
2965
  "summary": "Server-sent events (SSE) streams and rooms, delivery across workers, and a browser client",
1285
2966
  "description": "Server-sent events for @dokebi/web: authenticated streams, rooms, cross-worker fan-out over @dokebi/event-bus, and a browser client",
@@ -1291,7 +2972,7 @@
1291
2972
  "realtime"
1292
2973
  ],
1293
2974
  "maturity": "preview",
1294
- "core": "^0.4.0",
2975
+ "core": "^0.4.2",
1295
2976
  "provides": [
1296
2977
  "realtime.sse"
1297
2978
  ],
@@ -1303,7 +2984,7 @@
1303
2984
  "requires": [
1304
2985
  {
1305
2986
  "id": "dokebi-web",
1306
- "range": "^0.4.0"
2987
+ "range": "^0.4.3"
1307
2988
  }
1308
2989
  ],
1309
2990
  "optional": [
@@ -1318,13 +2999,19 @@
1318
2999
  ],
1319
3000
  "optionKeys": [
1320
3001
  "allowInCluster",
3002
+ "beforeStreamTimeoutMs",
1321
3003
  "busEvent",
3004
+ "busRetryAfterMs",
3005
+ "busWatchIntervalMs",
3006
+ "endTimeoutMs",
1322
3007
  "eventBus",
1323
3008
  "heartbeatMs",
3009
+ "logRepeatWindowMs",
1324
3010
  "maxBufferedBytes",
1325
3011
  "maxConnections",
1326
3012
  "maxEventBytes",
1327
3013
  "maxRooms",
3014
+ "reloadRetryMs",
1328
3015
  "retryMs",
1329
3016
  "shutdownTimeoutMs"
1330
3017
  ],
@@ -1334,10 +3021,110 @@
1334
3021
  },
1335
3022
  "install": "npx dokebi add @dokebi/sse"
1336
3023
  },
3024
+ {
3025
+ "id": "dokebi-static",
3026
+ "package": "@dokebi/static",
3027
+ "version": "0.1.0",
3028
+ "kind": "plugin",
3029
+ "summary": "Folders served under a path prefix (update feeds, installers, downloads) through @fastify/static: Range, ETag, conditional requests, cache rules per pattern, no listing, no path outside the root, uploads in progress hidden",
3030
+ "description": "Folders served under a path prefix on @dokebi/web through @fastify/static: Range, ETag, conditional requests and cache rules, never a path outside the root or a file being uploaded.",
3031
+ "keywords": [
3032
+ "dokebi",
3033
+ "dokebi-plugin",
3034
+ "web",
3035
+ "static",
3036
+ "files",
3037
+ "range"
3038
+ ],
3039
+ "maturity": "experimental",
3040
+ "core": "^0.4.2",
3041
+ "provides": [
3042
+ "static.files"
3043
+ ],
3044
+ "variants": [],
3045
+ "doesNot": [
3046
+ "No access policy: a mount is public on its hosts (private files belong in @dokebi/storage behind your own check)",
3047
+ "No directory listing, index files, uploads or files generated per request"
3048
+ ],
3049
+ "recipes": [],
3050
+ "requires": [
3051
+ {
3052
+ "id": "dokebi-web",
3053
+ "range": "^0.4.3",
3054
+ "package": "@dokebi/web",
3055
+ "automatic": true
3056
+ }
3057
+ ],
3058
+ "optional": [],
3059
+ "optionKeys": [
3060
+ "maxCacheRules",
3061
+ "maxHide",
3062
+ "maxMounts",
3063
+ "maxTypes",
3064
+ "mounts"
3065
+ ],
3066
+ "docs": {
3067
+ "ai": "AI.md",
3068
+ "readme": "README.md"
3069
+ },
3070
+ "install": "npx dokebi add @dokebi/static"
3071
+ },
3072
+ {
3073
+ "id": "dokebi-storage",
3074
+ "package": "@dokebi/storage",
3075
+ "version": "0.1.0",
3076
+ "kind": "plugin",
3077
+ "summary": "Local and S3 file storage with bounded multipart uploads and private downloads",
3078
+ "description": "Bounded streaming file storage with local and S3 drivers and optional multipart uploads.",
3079
+ "keywords": [
3080
+ "dokebi",
3081
+ "dokebi-plugin",
3082
+ "storage",
3083
+ "s3",
3084
+ "upload"
3085
+ ],
3086
+ "maturity": "experimental",
3087
+ "core": "^0.4.2",
3088
+ "provides": [
3089
+ "upload"
3090
+ ],
3091
+ "variants": [],
3092
+ "doesNot": [
3093
+ "No automatic public routes, access policy, virus scanning or image processing",
3094
+ "Single-object uploads; no resumable uploads or S3 multipart upload API"
3095
+ ],
3096
+ "recipes": [],
3097
+ "requires": [],
3098
+ "optional": [],
3099
+ "optionKeys": [
3100
+ "busyRetryAfterMs",
3101
+ "directory",
3102
+ "driver",
3103
+ "maxConcurrent",
3104
+ "maxFileBytes",
3105
+ "maxListPage",
3106
+ "maxWaiting",
3107
+ "s3",
3108
+ "signedUrlExpiresIn",
3109
+ "signedUrlMaxExpiresIn",
3110
+ "sniffBytes",
3111
+ "staleUploadMs",
3112
+ "tempDirectory",
3113
+ "timeoutMs",
3114
+ "timeoutRetryAfterMs",
3115
+ "unavailableRetryAfterMs",
3116
+ "waitTimeoutMs"
3117
+ ],
3118
+ "docs": {
3119
+ "ai": "AI.md",
3120
+ "readme": "README.md"
3121
+ },
3122
+ "install": "npx dokebi add @dokebi/storage"
3123
+ },
1337
3124
  {
1338
3125
  "id": "dokebi-template-ejs",
1339
3126
  "package": "@dokebi/template-ejs",
1340
- "version": "0.4.1",
3127
+ "version": "0.4.3",
1341
3128
  "kind": "plugin",
1342
3129
  "summary": "Compiles EJS templates at startup and renders them by name (layouts, partials, a scope per app)",
1343
3130
  "description": "Precompiled EJS templates for Dokebi apps: layouts, partials and per-app scopes.",
@@ -1365,7 +3152,8 @@
1365
3152
  ],
1366
3153
  "recipes": [
1367
3154
  "api",
1368
- "blog"
3155
+ "blog",
3156
+ "site"
1369
3157
  ],
1370
3158
  "requires": [],
1371
3159
  "optional": [],
@@ -1384,7 +3172,7 @@
1384
3172
  {
1385
3173
  "id": "dokebi-template-inertia",
1386
3174
  "package": "@dokebi/template-inertia",
1387
- "version": "0.4.1",
3175
+ "version": "0.4.4",
1388
3176
  "kind": "plugin",
1389
3177
  "summary": "Inertia (v3) server adapter: Vue 3 views, Vite build files and optional server-side rendering (SSR)",
1390
3178
  "description": "Inertia v3 server adapter for Dokebi: page JSON, Vite manifest assets and optional server-side rendering.",
@@ -1398,7 +3186,7 @@
1398
3186
  "ssr"
1399
3187
  ],
1400
3188
  "maturity": "preview",
1401
- "core": "^0.4.0",
3189
+ "core": "^0.4.2",
1402
3190
  "provides": [
1403
3191
  "view.inertia-vue",
1404
3192
  "view.inertia-vue-ssr"
@@ -1436,12 +3224,13 @@
1436
3224
  "Vue and Vite are not server dependencies (they are dev dependencies of the app)"
1437
3225
  ],
1438
3226
  "recipes": [
1439
- "blog"
3227
+ "blog",
3228
+ "site"
1440
3229
  ],
1441
3230
  "requires": [
1442
3231
  {
1443
3232
  "id": "dokebi-web",
1444
- "range": "^0.4.1"
3233
+ "range": "^0.4.3"
1445
3234
  }
1446
3235
  ],
1447
3236
  "optional": [
@@ -1451,15 +3240,22 @@
1451
3240
  }
1452
3241
  ],
1453
3242
  "optionKeys": [
3243
+ "assetCacheMaxBytes",
3244
+ "bootFallback",
3245
+ "devDepsRepairIntervalMs",
3246
+ "devHosts",
1454
3247
  "encryptHistory",
1455
3248
  "errorPage",
3249
+ "logRepeatWindowMs",
1456
3250
  "rootId",
3251
+ "routeIds",
1457
3252
  "scripts",
1458
3253
  "ssr",
1459
3254
  "styles",
1460
3255
  "title",
1461
3256
  "version",
1462
- "vite"
3257
+ "vite",
3258
+ "viteManifestMaxBytes"
1463
3259
  ],
1464
3260
  "docs": {
1465
3261
  "ai": "AI.md",
@@ -1467,12 +3263,132 @@
1467
3263
  },
1468
3264
  "install": "npx dokebi add @dokebi/template-inertia"
1469
3265
  },
3266
+ {
3267
+ "id": "dokebi-tree",
3268
+ "package": "@dokebi/tree",
3269
+ "version": "0.1.0",
3270
+ "kind": "plugin",
3271
+ "summary": "Trees in your own tables (categories, a members' holding tree): materialized paths kept on place and move, a subtree moved in one UPDATE in the caller's transaction and never under itself, sibling order with the parent locked, ancestors, subtree SQL conditions, and keyset pages in tree order that keep their parent context",
3272
+ "description": "Trees in your own tables for Dokebi: materialized paths, places and subtree moves in the caller's transaction (never under itself), sibling order, ancestors, subtree conditions, and pages in tree order that keep their parent context.",
3273
+ "keywords": [
3274
+ "dokebi",
3275
+ "dokebi-plugin",
3276
+ "tree",
3277
+ "hierarchy",
3278
+ "categories",
3279
+ "database"
3280
+ ],
3281
+ "maturity": "experimental",
3282
+ "core": "^0.4.2",
3283
+ "provides": [
3284
+ "data.tree"
3285
+ ],
3286
+ "variants": [],
3287
+ "doesNot": [
3288
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
3289
+ "Owns no table: your migration adds the parent, path and order columns",
3290
+ "Does not decide who may move what: your routes do"
3291
+ ],
3292
+ "recipes": [],
3293
+ "requires": [
3294
+ {
3295
+ "id": "dokebi-db",
3296
+ "range": "^0.4.1",
3297
+ "package": "@dokebi/db",
3298
+ "automatic": true
3299
+ }
3300
+ ],
3301
+ "optional": [
3302
+ {
3303
+ "id": "dokebi-lock",
3304
+ "range": "^0.4.0"
3305
+ }
3306
+ ],
3307
+ "optionKeys": [
3308
+ "busyRetryAfterMs",
3309
+ "connection",
3310
+ "lockBackend",
3311
+ "lockWaitTimeoutMs",
3312
+ "maxMove",
3313
+ "maxPage",
3314
+ "maxRepair"
3315
+ ],
3316
+ "docs": {
3317
+ "ai": "AI.md",
3318
+ "readme": "README.md"
3319
+ },
3320
+ "install": "npx dokebi add @dokebi/tree"
3321
+ },
3322
+ {
3323
+ "id": "dokebi-two-factor",
3324
+ "package": "@dokebi/two-factor",
3325
+ "version": "0.1.0",
3326
+ "kind": "plugin",
3327
+ "summary": "Two-factor sign-in for @dokebi/auth: TOTP codes of authenticator apps (secrets encrypted at rest, a code good once), ten recovery codes kept as HMACs, a second step after the password, remembered devices, lockout after wrong codes, and a code confirmation for the re-authentication page",
3328
+ "description": "Two-factor sign-in for @dokebi/auth: TOTP codes of authenticator apps (secrets encrypted at rest), recovery codes stored as HMACs, a second step after the password, remembered devices, lockout, and a code confirmation for re-authentication.",
3329
+ "keywords": [
3330
+ "dokebi",
3331
+ "dokebi-plugin",
3332
+ "two-factor",
3333
+ "2fa",
3334
+ "totp",
3335
+ "mfa",
3336
+ "auth"
3337
+ ],
3338
+ "maturity": "experimental",
3339
+ "core": "^0.4.2",
3340
+ "provides": [
3341
+ "two-factor"
3342
+ ],
3343
+ "variants": [],
3344
+ "doesNot": [
3345
+ "No MongoDB (PostgreSQL, MySQL 8, MariaDB 10.6+)",
3346
+ "No SMS, e-mail codes, WebAuthn or passkeys: TOTP and recovery codes only",
3347
+ "Does not decide who must use it: the application sends those users to its setup page",
3348
+ "Cannot stop a login route that calls request.auth.attempt itself: use twoFactor.attempt there",
3349
+ "Does not draw the QR code: it answers the otpauth URI"
3350
+ ],
3351
+ "recipes": [],
3352
+ "requires": [
3353
+ {
3354
+ "id": "dokebi-db",
3355
+ "range": "^0.4.1",
3356
+ "package": "@dokebi/db",
3357
+ "automatic": true
3358
+ },
3359
+ {
3360
+ "id": "dokebi-auth",
3361
+ "range": "^0.4.1",
3362
+ "package": "@dokebi/auth",
3363
+ "automatic": true
3364
+ }
3365
+ ],
3366
+ "optional": [],
3367
+ "optionKeys": [
3368
+ "challengeMs",
3369
+ "confirmMs",
3370
+ "connection",
3371
+ "enrollMs",
3372
+ "issuer",
3373
+ "lockMs",
3374
+ "maxFailures",
3375
+ "maxTries",
3376
+ "rememberDeviceMs",
3377
+ "secrets",
3378
+ "secretsEnv"
3379
+ ],
3380
+ "docs": {
3381
+ "ai": "AI.md",
3382
+ "readme": "README.md"
3383
+ },
3384
+ "install": "npx dokebi add @dokebi/two-factor"
3385
+ },
1470
3386
  {
1471
3387
  "id": "dokebi-web",
1472
3388
  "package": "@dokebi/web",
1473
- "version": "0.4.1",
3389
+ "version": "0.4.3",
1474
3390
  "kind": "plugin",
1475
- "summary": "Fastify 5 HTTP server (503 before ready, health paths, drain, zero-downtime cluster replacement)",
3391
+ "summary": "Fastify 5 HTTP server (503 before ready, health paths, drain, zero-downtime cluster replacement, security headers and overload protection on by default)",
1476
3392
  "description": "Fastify 5 HTTP server owned by a Dokebi app: 503 before ready, health routes, drain and cluster handover.",
1477
3393
  "keywords": [
1478
3394
  "dokebi",
@@ -1482,38 +3398,63 @@
1482
3398
  "fastify"
1483
3399
  ],
1484
3400
  "maturity": "preview",
1485
- "core": "^0.4.0",
3401
+ "core": "^0.4.2",
1486
3402
  "provides": [
1487
- "web.http"
3403
+ "web.http",
3404
+ "web.overload",
3405
+ "web.security-headers"
1488
3406
  ],
1489
3407
  "variants": [],
1490
3408
  "doesNot": [
1491
- "No built-in authentication, rate limit, CORS or security headers",
3409
+ "No built-in authentication, rate limit or CORS (CORS is @dokebi/cors)",
1492
3410
  "No file upload (multipart)"
1493
3411
  ],
1494
3412
  "recipes": [
1495
3413
  "api",
1496
- "blog"
3414
+ "blog",
3415
+ "site"
1497
3416
  ],
1498
3417
  "requires": [],
1499
3418
  "optional": [
1500
3419
  {
1501
3420
  "id": "dokebi-health",
1502
3421
  "range": "^0.4.0"
3422
+ },
3423
+ {
3424
+ "id": "dokebi-logger",
3425
+ "range": "^0.1.0"
1503
3426
  }
1504
3427
  ],
1505
3428
  "optionKeys": [
3429
+ "binaryBody",
3430
+ "compression",
1506
3431
  "developmentErrors",
3432
+ "drainGraceMs",
3433
+ "drainIdleMs",
1507
3434
  "formBody",
1508
3435
  "formMaxFields",
3436
+ "formOldInputMaxChars",
3437
+ "formRefererMaxLength",
1509
3438
  "healthRoutes",
1510
3439
  "host",
3440
+ "linger",
3441
+ "logRepeatWindowMs",
1511
3442
  "maxBodyBytes",
3443
+ "maxRouteBodyBytes",
3444
+ "overload",
1512
3445
  "port",
1513
3446
  "requestTimeoutMs",
3447
+ "routeIds",
1514
3448
  "sealDriftMs",
1515
3449
  "sealExclude",
1516
- "trustProxy"
3450
+ "sealRetryAfterMs",
3451
+ "securityHeaders",
3452
+ "transientLogLevel",
3453
+ "transientLogMaxKeys",
3454
+ "transientLogWindowMs",
3455
+ "trustProxy",
3456
+ "unavailableRetryAfterMs",
3457
+ "unknownFields"
1517
3458
  ],
1518
3459
  "docs": {
1519
3460
  "ai": "AI.md",
@@ -1524,7 +3465,7 @@
1524
3465
  {
1525
3466
  "id": "dokebi-websocket",
1526
3467
  "package": "@dokebi/websocket",
1527
- "version": "0.4.0",
3468
+ "version": "0.4.2",
1528
3469
  "kind": "plugin",
1529
3470
  "summary": "WebSocket routes, rooms and broadcast on top of dokebi-web (at most once)",
1530
3471
  "description": "WebSocket routes on @dokebi/web with rooms and broadcast across servers through @dokebi/websocket-hub.",
@@ -1536,7 +3477,7 @@
1536
3477
  "realtime"
1537
3478
  ],
1538
3479
  "maturity": "preview",
1539
- "core": "^0.4.0",
3480
+ "core": "^0.4.2",
1540
3481
  "provides": [
1541
3482
  "realtime.websocket"
1542
3483
  ],
@@ -1552,7 +3493,7 @@
1552
3493
  },
1553
3494
  {
1554
3495
  "id": "dokebi-web",
1555
- "range": "^0.4.0"
3496
+ "range": "^0.4.3"
1556
3497
  }
1557
3498
  ],
1558
3499
  "optional": [
@@ -1562,9 +3503,12 @@
1562
3503
  }
1563
3504
  ],
1564
3505
  "optionKeys": [
3506
+ "beforeUpgradeTimeoutMs",
1565
3507
  "heartbeatMs",
3508
+ "hubFanOut",
1566
3509
  "hubWaitMs",
1567
3510
  "idleTimeoutMs",
3511
+ "logRepeatWindowMs",
1568
3512
  "maxBufferedBytes",
1569
3513
  "maxConnections",
1570
3514
  "maxPayloadBytes",
@@ -1581,7 +3525,7 @@
1581
3525
  {
1582
3526
  "id": "dokebi-websocket-hub",
1583
3527
  "package": "@dokebi/websocket-hub",
1584
- "version": "0.4.0",
3528
+ "version": "0.4.2",
1585
3529
  "kind": "plugin",
1586
3530
  "summary": "Hub of WebSocket clients, rooms, broadcast and kick (usually used through @dokebi/websocket)",
1587
3531
  "description": "WebSocket hub for Dokebi: connection roster, rooms, broadcast and kick across workers and servers.",
@@ -1593,7 +3537,7 @@
1593
3537
  "realtime"
1594
3538
  ],
1595
3539
  "maturity": "preview",
1596
- "core": "^0.4.0",
3540
+ "core": "^0.4.2",
1597
3541
  "provides": [
1598
3542
  "realtime.hub"
1599
3543
  ],
@@ -1616,10 +3560,13 @@
1616
3560
  "optionKeys": [
1617
3561
  "advertiseHost",
1618
3562
  "allowInCluster",
3563
+ "broadcastShare",
1619
3564
  "host",
1620
3565
  "leaseMs",
3566
+ "logRepeatWindowMs",
1621
3567
  "maxConnections",
1622
3568
  "maxFrameBytes",
3569
+ "maxNonces",
1623
3570
  "maxOwners",
1624
3571
  "maxPayloadBytes",
1625
3572
  "maxPending",
@@ -1645,7 +3592,7 @@
1645
3592
  {
1646
3593
  "id": "dokebi-worker",
1647
3594
  "package": "@dokebi/worker",
1648
- "version": "0.4.0",
3595
+ "version": "0.4.1",
1649
3596
  "kind": "plugin",
1650
3597
  "summary": "Runs CPU-heavy functions in a worker thread pool",
1651
3598
  "description": "Worker thread pool for CPU-heavy functions in a Dokebi app.",
@@ -1657,7 +3604,7 @@
1657
3604
  "pool"
1658
3605
  ],
1659
3606
  "maturity": "preview",
1660
- "core": "^0.4.0",
3607
+ "core": "^0.4.2",
1661
3608
  "provides": [
1662
3609
  "worker.thread"
1663
3610
  ],
@@ -1666,7 +3613,9 @@
1666
3613
  "Not several processes (cluster)",
1667
3614
  "Not a distributed job queue (@dokebi/job)"
1668
3615
  ],
1669
- "recipes": [],
3616
+ "recipes": [
3617
+ "site"
3618
+ ],
1670
3619
  "requires": [],
1671
3620
  "optional": [],
1672
3621
  "optionKeys": [
@@ -1688,41 +3637,44 @@
1688
3637
  "recipes": [
1689
3638
  {
1690
3639
  "name": "api",
1691
- "summary": "JSON API server (one app, one JSON module and a database of your choice)",
3640
+ "summary": "JSON API for other programs (one app, one JSON module and a database of your choice); builds no browser screens — when people use the service in a browser, use site or blog",
1692
3641
  "match": [
1693
3642
  "api",
1694
3643
  "rest",
1695
3644
  "json",
1696
3645
  "backend"
1697
3646
  ],
3647
+ "screens": false,
1698
3648
  "capabilities": [
1699
3649
  "web.apps",
1700
- "web.modules"
3650
+ "web.modules",
3651
+ "logging.targets"
1701
3652
  ],
1702
3653
  "questions": [
1703
3654
  "db",
1704
- "devInfra"
3655
+ "devInfra",
3656
+ "mail"
1705
3657
  ],
1706
3658
  "fixed": {
1707
- "view": "ejs",
1708
3659
  "admin": "none"
1709
3660
  },
1710
3661
  "module": {
1711
3662
  "name": "items",
1712
3663
  "views": false,
1713
- "why": "A feature module that answers JSON: write the API in its routes.ts and controllers"
3664
+ "why": "A feature module that answers JSON: bind routes in routes.ts; controllers call services, and models own persistence"
1714
3665
  },
1715
3666
  "web": {
1716
3667
  "port": 3000,
1717
- "domain": "localhost"
3668
+ "domain": "auto"
1718
3669
  },
1719
3670
  "then": [
1720
- "Attach the module to the app: modules: { \"/api/items\": items } in src/apps/main/app.ts"
3671
+ "Attach the module to the app: modules: { \"/api/items\": items } in src/apps/main/app.ts",
3672
+ "Security headers and overload protection of @dokebi/web are on by default (D-0149, D-0150): keep them. Only when the user asks that browser pages on other origins call this API, add @dokebi/cors and list those origins (D-0148)"
1721
3673
  ]
1722
3674
  },
1723
3675
  {
1724
3676
  "name": "blog",
1725
- "summary": "A blog web app with a post list and post pages (choose the views, database, development database and admin area)",
3677
+ "summary": "A blog web app with browser screens: a post list and post pages (choose the views (default Inertia + Vue), database, development database and admin area)",
1726
3678
  "match": [
1727
3679
  "blog",
1728
3680
  "board",
@@ -1732,27 +3684,72 @@
1732
3684
  ],
1733
3685
  "capabilities": [
1734
3686
  "web.apps",
1735
- "web.modules"
3687
+ "web.modules",
3688
+ "csrf",
3689
+ "logging.targets"
1736
3690
  ],
1737
3691
  "questions": [
1738
3692
  "view",
1739
3693
  "db",
1740
3694
  "devInfra",
1741
- "admin"
3695
+ "admin",
3696
+ "mail"
1742
3697
  ],
1743
3698
  "module": {
1744
3699
  "name": "posts",
1745
- "why": "The module for the posts feature: write its controllers, models and routes here"
3700
+ "why": "The module for the posts feature: write its controllers, services, models and routes here"
1746
3701
  },
1747
3702
  "web": {
1748
3703
  "port": 3000,
1749
- "domain": "localhost",
3704
+ "domain": "auto",
1750
3705
  "forms": true
1751
3706
  },
1752
3707
  "then": [
3708
+ "Web application requirements: install the i18n capability and verify sealing (the recipe installed security.seal and sealed the browser hosts). Inertia + Vue is the default view unless the user chose another: keep the sealInertia bootstrap of views/app.js before createInertiaApp and dokebiInertia() in vite.config.ts (it makes every import of @dokebi/seal/http load the served /dokebi/seal/http.mjs); the first page and every visit then go sealed. With inertia-vue-ssr the recipe left the hosts unsealed, because SSR writes the props into plain HTML (SEAL_SSR_CONFLICT): tell the user and let them choose. With EJS, the recipe sealed the host and turned on dokebi-web.formBody (sealed forms are form bodies); keep <%- data.seal?.head ?? '' %> in the shared layout <head> (first, before an import map of its own); it loads @dokebi/seal/forms, which seals every post form with no per-form code and moves _csrf into the x-csrf-token header of a multipart form itself. Submit from a script with form.requestSubmit() or submitSealedForm(form), never form.submit() (it goes out plain: 415); data-seal=\"off\" only for a deliberate exception, never to make a plain form post pass (a sealed host answers it 415). Application API requests use @dokebi/seal/http sealedFetch, with the same module instance as that bootstrap (Inertia: import { sealedFetch } from @dokebi/seal/http in the page; dokebiInertia() makes it the served module views/app.js uses), and the documented CSRF header. Do not substitute raw fetch or direct Inertia HTTP calls, and never turn sealing off to make a form work or a request pass",
3709
+ "Provide Korean and English UI, validation messages and persistent language switching. Keep matching feature catalogs under src/shared/locales/ko and src/shared/locales/en and use the documented key/default pair. Read the installed i18n AI guide; Vue pages use useI18n() of @dokebi/template-inertia/i18n",
3710
+ "Confirm single application process or process cluster and the worker count with the user. The blog recipe does not configure process topology. For cluster, discover process.cluster, install the library from its catalog entry, and read node_modules/@dokebi/cluster/AI.md. Keep the parent in src/main.ts and the worker factory in src/infra/runtime/application.ts; configure the count in YAML and share its absolute DOKEBI_CONFIG path. Provision shared state and queues actually used by the app before checking all workers and graceful shutdown. Job consumers and CPU threads are separate choices; there is no recipe --workers flag",
1753
3711
  "Attach the module to the app: modules: { \"/posts\": posts } in src/apps/main/app.ts (posts = the default export of src/shared/modules/posts/module.ts)",
1754
- "Write the post list (GET /posts) and the post page (GET /posts/:id) in the routes.ts and controllers of the module",
1755
- "The write form (GET /posts/new → POST /posts) reads request.body (with EJS the recipe turned on dokebi-web.formBody). With --admin none it is a public form (no login); with app-code it belongs to the admin app. Either way it needs a CSRF token check in app code (csrf is absent). A refused post throws validationError({ title: \"Required\" }) from @dokebi/web (D-0074): an EJS form shows data.errors.title and data.old.title (with @dokebi/session the refused post goes back to the form, 303; without it views/default/errors/422.ejs or errors/error.ejs shows the fields), an Inertia form gets them in form.errors (submit with only). Edit the shared layout src/shared/views/layouts/main.ejs where it is"
3712
+ "Bind the post list (GET /posts) and post page (GET /posts/:id) in routes.ts. Controllers handle HTTP through services, and services provide data through models. Keep SQL in models; use app-specific, shared or module services according to ownership",
3713
+ "The write form (GET /posts/new → POST /posts) reads request.body (with EJS the recipe sealed the host and turned on dokebi-web.formBody: @dokebi/seal/forms sends the form sealed and the handler gets the same body). With --admin none it is a public form (no login); with login both routes get config: { auth: \"required\" } (only signed-in users write); with app-code it belongs to the admin app. @dokebi/csrf checks every POST: an EJS form writes <%- data.csrf.field %> inside the form, an Inertia form needs nothing (XSRF-TOKEN), sealedFetch sends the documented X-XSRF-TOKEN header through the shared adapter. A refused post throws validationError({ title: \"Required\" }) from @dokebi/web (D-0074): an EJS form shows data.errors.title and data.old.title (with @dokebi/session the refused post goes back to the form, 303; without it views/default/errors/422.ejs or errors/error.ejs shows the fields), an Inertia form gets them in form.errors (submit with only). Edit the shared layout only when the app actually uses EJS web pages; keep mail templates separately"
3714
+ ]
3715
+ },
3716
+ {
3717
+ "name": "site",
3718
+ "summary": "A browser app people use (sign up, browse, press buttons, read lists): public web, admin and API apps of your choice with explicit domains, auth realms and an optional worker (asks the view; default Inertia + Vue)",
3719
+ "match": [
3720
+ "site",
3721
+ "web",
3722
+ "browser",
3723
+ "service",
3724
+ "screens",
3725
+ "multiapp",
3726
+ "admin",
3727
+ "worker",
3728
+ "domains"
3729
+ ],
3730
+ "capabilities": [
3731
+ "web.apps",
3732
+ "web.session",
3733
+ "csrf",
3734
+ "auth",
3735
+ "logging.targets"
3736
+ ],
3737
+ "questions": [
3738
+ "apps",
3739
+ "domains",
3740
+ "realms",
3741
+ "view",
3742
+ "worker"
3743
+ ],
3744
+ "web": {
3745
+ "port": 3000,
3746
+ "domain": "auto"
3747
+ },
3748
+ "then": [
3749
+ "Connect the realm auth extensions to your own user tables before enabling login",
3750
+ "Security headers and overload protection of @dokebi/web are on by default (D-0149, D-0150): keep them, and change a CSP directive in dokebi.yaml rather than turning them off. Add @dokebi/cors only when the user asks for pages on other origins (D-0148)",
3751
+ "For web apps, install i18n (the recipe installed security.seal and sealed the web and admin hosts; api stays plain). Use @dokebi/seal/http for application API requests and preserve its Inertia bridge on the same module instance. Inertia web and admin apps: keep the sealInertia bootstrap of views/app.js before createInertiaApp and dokebiInertia() in vite.config.ts, and import sealedFetch from @dokebi/seal/http (the plugin makes it the same served module). With server rendering (inertia-vue-ssr) the recipe left them unsealed, because SSR writes the props into plain HTML (SEAL_SSR_CONFLICT): tell the user and let them choose. EJS web and admin apps are sealed hosts with dokebi-web.formBody on (the recipe did both): keep <%- data.seal?.head ?? '' %> in the shared layout <head> so @dokebi/seal/forms seals their post forms (no per-form code; a multipart form gets its _csrf moved into the x-csrf-token header); submit from a script with form.requestSubmit() or submitSealedForm(form), never form.submit(); never turn sealing off to make a form work. Follow the documented CSRF header contract; resolve explicit SSR conflicts before choosing configuration",
3752
+ "For web apps, implement Korean and English catalogs by feature under src/shared/locales/ko and src/shared/locales/en, translated forms and validation, and persistent language switching; verify both languages"
1756
3753
  ]
1757
3754
  }
1758
3755
  ],
@@ -1761,53 +3758,197 @@
1761
3758
  "id": "admin",
1762
3759
  "flag": "--admin",
1763
3760
  "from": "fixed",
1764
- "question": "Do you need an admin area (a login that protects writing and managing posts)?",
3761
+ "question": "Do you need an admin area (a login that protects writing and managing content)?",
1765
3762
  "default": "none",
1766
- "defaultReason": "There is no auth plugin, so the app would have to write the login itself. When the request does not mention an admin area, none is made; a public write form is still possible (it needs no login)",
3763
+ "defaultReason": "When the request does not mention an admin area or a login, none is made. A public write form is still possible (it needs no login, and @dokebi/csrf protects it from other sites); choose login for an admin login",
1767
3764
  "options": [
1768
3765
  {
1769
- "value": "none",
1770
- "label": "None (no login and no admin area; a public write form is still possible)",
3766
+ "value": "none",
3767
+ "label": "None (no login and no admin area; a public write form is still possible)",
3768
+ "capability": null,
3769
+ "available": true,
3770
+ "absent": false,
3771
+ "plugins": [],
3772
+ "alsoInstalls": [],
3773
+ "needs": [],
3774
+ "absentFeatures": [],
3775
+ "pros": [
3776
+ "The simplest choice with the smallest attack surface",
3777
+ "Content can still be written in the browser with a public write form (with EJS the recipe turns on dokebi-web.formBody for it), protected from other sites by @dokebi/csrf"
3778
+ ],
3779
+ "cons": [
3780
+ "Without a login anyone who can open the site can use the write form"
3781
+ ]
3782
+ },
3783
+ {
3784
+ "value": "login",
3785
+ "label": "Admin login (only signed-in users write)",
1771
3786
  "capability": null,
1772
3787
  "available": true,
1773
3788
  "absent": false,
1774
- "plugins": [],
3789
+ "plugins": [
3790
+ "@dokebi/auth",
3791
+ "@dokebi/csrf",
3792
+ "@dokebi/session"
3793
+ ],
1775
3794
  "alsoInstalls": [],
1776
3795
  "needs": [],
1777
- "absentFeatures": [],
3796
+ "when": {
3797
+ "db": "!none"
3798
+ },
3799
+ "absentFeatures": [
3800
+ "admin.ui"
3801
+ ],
1778
3802
  "pros": [
1779
- "The simplest choice with the smallest attack surface",
1780
- "Posts can still be written in the browser with a public write form (with EJS the recipe turns on dokebi-web.formBody for it)"
3803
+ "A login page, sign-out, a protected admin page and a first admin account are made (@dokebi/auth session login); writing is for signed-in users only",
3804
+ "The plugin hashes and checks passwords (argon2id or scrypt), throttles logins and shows one sentence for every failed login",
3805
+ "The user table is the app's own (a migration with the app's column names); the first admin's password is generated into dokebi.yaml and never printed"
1781
3806
  ],
1782
3807
  "cons": [
1783
- "Without a login anyone who can open the site can use the write form",
1784
- "The write form has no CSRF protection until the app adds a token check (csrf is absent)"
3808
+ "Password recovery requires an installed mail plugin with a job queue and an email address mapped by the extension",
3809
+ "Needs a database for the user table (not available with --db none)",
3810
+ "Sign-up and verification screens are app code using @dokebi/auth; two-factor sign-in is a separate package (npx dokebi add @dokebi/two-factor)"
1785
3811
  ]
1786
3812
  },
1787
3813
  {
1788
3814
  "value": "app-code",
1789
- "label": "An admin app written in app code",
3815
+ "label": "An admin app whose login you write yourself (with @dokebi/auth)",
1790
3816
  "capability": null,
1791
3817
  "available": true,
1792
3818
  "absent": false,
1793
3819
  "plugins": [
3820
+ "@dokebi/csrf",
1794
3821
  "@dokebi/session"
1795
3822
  ],
1796
3823
  "alsoInstalls": [],
1797
3824
  "needs": [],
1798
3825
  "absentFeatures": [
1799
- "auth",
1800
3826
  "admin.ui"
1801
3827
  ],
1802
3828
  "pros": [
1803
- "One app on its own domain (such as admin.localhost) with sessions is set up"
3829
+ "One app on its own domain (admin.<project>.localhost) with sessions and CSRF protection is set up",
3830
+ "You decide the login screens and the user table; add @dokebi/auth for the login itself"
1804
3831
  ],
1805
3832
  "cons": [
1806
- "Login, password hashing, permission checks and CSRF must be written in app code (there is no plugin)"
3833
+ "The login, the user table and the permission checks are app code to write (npx dokebi add @dokebi/auth, then its extension class)"
1807
3834
  ]
1808
3835
  }
1809
3836
  ]
1810
3837
  },
3838
+ {
3839
+ "id": "apps",
3840
+ "flag": "--apps",
3841
+ "from": "fixed",
3842
+ "question": "Which HTTP apps belong in this site?",
3843
+ "default": "web,admin,api",
3844
+ "defaultReason": "Separate public pages, administration and API domains",
3845
+ "options": [
3846
+ {
3847
+ "value": "web",
3848
+ "label": "web",
3849
+ "capability": null,
3850
+ "available": true,
3851
+ "absent": false,
3852
+ "plugins": [],
3853
+ "alsoInstalls": [],
3854
+ "needs": [],
3855
+ "absentFeatures": [],
3856
+ "pros": [],
3857
+ "cons": []
3858
+ },
3859
+ {
3860
+ "value": "admin",
3861
+ "label": "admin",
3862
+ "capability": null,
3863
+ "available": true,
3864
+ "absent": false,
3865
+ "plugins": [],
3866
+ "alsoInstalls": [],
3867
+ "needs": [],
3868
+ "absentFeatures": [],
3869
+ "pros": [],
3870
+ "cons": []
3871
+ },
3872
+ {
3873
+ "value": "api",
3874
+ "label": "api",
3875
+ "capability": null,
3876
+ "available": true,
3877
+ "absent": false,
3878
+ "plugins": [],
3879
+ "alsoInstalls": [],
3880
+ "needs": [],
3881
+ "absentFeatures": [],
3882
+ "pros": [],
3883
+ "cons": []
3884
+ },
3885
+ {
3886
+ "value": "web,admin",
3887
+ "label": "web, admin",
3888
+ "capability": null,
3889
+ "available": true,
3890
+ "absent": false,
3891
+ "plugins": [],
3892
+ "alsoInstalls": [],
3893
+ "needs": [],
3894
+ "absentFeatures": [],
3895
+ "pros": [],
3896
+ "cons": []
3897
+ },
3898
+ {
3899
+ "value": "web,api",
3900
+ "label": "web, api",
3901
+ "capability": null,
3902
+ "available": true,
3903
+ "absent": false,
3904
+ "plugins": [],
3905
+ "alsoInstalls": [],
3906
+ "needs": [],
3907
+ "absentFeatures": [],
3908
+ "pros": [],
3909
+ "cons": []
3910
+ },
3911
+ {
3912
+ "value": "admin,api",
3913
+ "label": "admin, api",
3914
+ "capability": null,
3915
+ "available": true,
3916
+ "absent": false,
3917
+ "plugins": [],
3918
+ "alsoInstalls": [],
3919
+ "needs": [],
3920
+ "absentFeatures": [],
3921
+ "pros": [],
3922
+ "cons": []
3923
+ },
3924
+ {
3925
+ "value": "web,admin,api",
3926
+ "label": "web, admin, api",
3927
+ "capability": null,
3928
+ "available": true,
3929
+ "absent": false,
3930
+ "plugins": [],
3931
+ "alsoInstalls": [],
3932
+ "needs": [],
3933
+ "absentFeatures": [],
3934
+ "pros": [],
3935
+ "cons": []
3936
+ },
3937
+ {
3938
+ "value": "none",
3939
+ "label": "No HTTP apps (worker only)",
3940
+ "capability": null,
3941
+ "available": true,
3942
+ "absent": false,
3943
+ "plugins": [],
3944
+ "alsoInstalls": [],
3945
+ "needs": [],
3946
+ "absentFeatures": [],
3947
+ "pros": [],
3948
+ "cons": []
3949
+ }
3950
+ ]
3951
+ },
1811
3952
  {
1812
3953
  "id": "db",
1813
3954
  "flag": "--db",
@@ -1898,7 +4039,7 @@
1898
4039
  "needs": [],
1899
4040
  "absentFeatures": [],
1900
4041
  "pros": [
1901
- "Stores documents (JSON) without a schema, handy for data whose shape changes often, such as posts"
4042
+ "Stores documents (JSON) without a schema, handy for data whose shape changes often, such as user-written content"
1902
4043
  ],
1903
4044
  "cons": [
1904
4045
  "Relations, joins and transactions are less convenient than in a SQL database",
@@ -1990,14 +4131,126 @@
1990
4131
  }
1991
4132
  ]
1992
4133
  },
4134
+ {
4135
+ "id": "domains",
4136
+ "flag": "--domains",
4137
+ "from": "fixed",
4138
+ "input": "text",
4139
+ "question": "Domain for each selected HTTP app: web=www.example.com,admin=admin.example.com,api=api.example.com. Use auto for <app>.<project>.localhost, the development hosts of this project (the project name from package.json, else the folder name), so two Dokebi projects on one machine never share cookies.",
4140
+ "default": "auto",
4141
+ "defaultReason": "Local development hosts unique to this project: browsers keep cookies per host, not per port",
4142
+ "options": []
4143
+ },
4144
+ {
4145
+ "id": "mail",
4146
+ "flag": "--mail",
4147
+ "from": "fixed",
4148
+ "question": "How should development mail be delivered?",
4149
+ "default": "none",
4150
+ "defaultReason": "No mail is installed unless the user requests mail or password recovery",
4151
+ "options": [
4152
+ {
4153
+ "value": "none",
4154
+ "label": "No development mail",
4155
+ "capability": null,
4156
+ "available": true,
4157
+ "absent": false,
4158
+ "plugins": [],
4159
+ "alsoInstalls": [],
4160
+ "needs": [],
4161
+ "absentFeatures": [],
4162
+ "pros": [
4163
+ "No extra services or credentials"
4164
+ ],
4165
+ "cons": [
4166
+ "Password recovery requires mail and a durable job queue"
4167
+ ]
4168
+ },
4169
+ {
4170
+ "value": "mailpit",
4171
+ "label": "Local Mailpit inbox with a durable job queue",
4172
+ "capability": null,
4173
+ "available": true,
4174
+ "absent": false,
4175
+ "plugins": [
4176
+ "@dokebi/job",
4177
+ "@dokebi/mail",
4178
+ "@dokebi/nats"
4179
+ ],
4180
+ "alsoInstalls": [],
4181
+ "needs": [
4182
+ "docker"
4183
+ ],
4184
+ "absentFeatures": [],
4185
+ "pros": [
4186
+ "Starts Mailpit and NATS in compose.mail.yaml with free loopback ports",
4187
+ "Messages stay on this computer and are never delivered externally"
4188
+ ],
4189
+ "cons": [
4190
+ "Docker is required",
4191
+ "Not a production mail server"
4192
+ ]
4193
+ },
4194
+ {
4195
+ "value": "smtp",
4196
+ "label": "Existing SMTP server",
4197
+ "capability": null,
4198
+ "available": true,
4199
+ "absent": false,
4200
+ "plugins": [
4201
+ "@dokebi/mail"
4202
+ ],
4203
+ "alsoInstalls": [],
4204
+ "needs": [],
4205
+ "absentFeatures": [],
4206
+ "pros": [
4207
+ "Uses an existing mail service"
4208
+ ],
4209
+ "cons": [
4210
+ "First install mail and configure transport and sender in dokebi.yaml without putting credentials on a command line",
4211
+ "Login recovery also requires a provisioned job queue"
4212
+ ]
4213
+ },
4214
+ {
4215
+ "value": "ses",
4216
+ "label": "Amazon SES v2",
4217
+ "capability": null,
4218
+ "available": true,
4219
+ "absent": false,
4220
+ "plugins": [
4221
+ "@dokebi/mail"
4222
+ ],
4223
+ "alsoInstalls": [],
4224
+ "needs": [],
4225
+ "absentFeatures": [],
4226
+ "pros": [
4227
+ "Uses an existing SES account and verified sender"
4228
+ ],
4229
+ "cons": [
4230
+ "First install mail and configure the region and verified sender in dokebi.yaml and AWS credentials through the documented options or environment",
4231
+ "Login recovery also requires a provisioned job queue"
4232
+ ]
4233
+ }
4234
+ ]
4235
+ },
4236
+ {
4237
+ "id": "realms",
4238
+ "flag": "--realms",
4239
+ "from": "fixed",
4240
+ "input": "text",
4241
+ "question": "Authentication realm for each selected HTTP app: web=members,admin=staff,api=members. Use auto for separate app realms.",
4242
+ "default": "auto",
4243
+ "defaultReason": "Separate user sets for each app",
4244
+ "options": []
4245
+ },
1993
4246
  {
1994
4247
  "id": "view",
1995
4248
  "flag": "--view",
1996
4249
  "from": "variants",
1997
4250
  "capabilityPrefix": "view.",
1998
4251
  "question": "What should the views (screens) be built with?",
1999
- "default": "ejs",
2000
- "defaultReason": "No build step and it runs right away on one server. Enough for blog screens such as the post list and a post page",
4252
+ "default": "inertia-vue",
4253
+ "defaultReason": "The default for browser apps. It needs a Vite build (npm run build)",
2001
4254
  "options": [
2002
4255
  {
2003
4256
  "value": "ejs",
@@ -2073,7 +4326,8 @@
2073
4326
  ],
2074
4327
  "cons": [
2075
4328
  "Two builds (browser and server) and the most configuration",
2076
- "Uses more server memory and CPU"
4329
+ "Uses more server memory and CPU",
4330
+ "Cannot be sealed (@dokebi/seal): the server writes the page props into the first HTML as plain text, so its browser hosts stay unsealed"
2077
4331
  ]
2078
4332
  },
2079
4333
  {
@@ -2091,29 +4345,69 @@
2091
4345
  "cons": []
2092
4346
  }
2093
4347
  ]
4348
+ },
4349
+ {
4350
+ "id": "worker",
4351
+ "flag": "--worker",
4352
+ "from": "fixed",
4353
+ "question": "Does this site need a background job queue or CPU worker pool?",
4354
+ "default": "none",
4355
+ "defaultReason": "No worker infrastructure unless the application needs it",
4356
+ "options": [
4357
+ {
4358
+ "value": "none",
4359
+ "label": "No worker",
4360
+ "capability": null,
4361
+ "available": true,
4362
+ "absent": false,
4363
+ "plugins": [],
4364
+ "alsoInstalls": [],
4365
+ "needs": [],
4366
+ "absentFeatures": [],
4367
+ "pros": [],
4368
+ "cons": []
4369
+ },
4370
+ {
4371
+ "value": "thread",
4372
+ "label": "CPU thread pool",
4373
+ "capability": null,
4374
+ "available": true,
4375
+ "absent": false,
4376
+ "plugins": [
4377
+ "@dokebi/worker"
4378
+ ],
4379
+ "alsoInstalls": [],
4380
+ "needs": [],
4381
+ "absentFeatures": [],
4382
+ "pros": [],
4383
+ "cons": []
4384
+ },
4385
+ {
4386
+ "value": "job",
4387
+ "label": "Durable NATS job queue",
4388
+ "capability": null,
4389
+ "available": true,
4390
+ "absent": false,
4391
+ "plugins": [
4392
+ "@dokebi/job",
4393
+ "@dokebi/nats"
4394
+ ],
4395
+ "alsoInstalls": [],
4396
+ "needs": [],
4397
+ "absentFeatures": [],
4398
+ "pros": [],
4399
+ "cons": [
4400
+ "Configure NATS and provision the queue before starting; no database or infrastructure is invented"
4401
+ ]
4402
+ }
4403
+ ]
2094
4404
  }
2095
4405
  ],
2096
4406
  "absent": [
2097
4407
  {
2098
4408
  "capability": "admin.ui",
2099
4409
  "summary": "There are no generated admin screens (CRUD generator)",
2100
- "instead": "Create one more app with dokebi app add admin --domain admin.localhost and write its screens in app code (there is no auth either, so the app writes the login)",
2101
- "verify": [
2102
- "no-provider"
2103
- ]
2104
- },
2105
- {
2106
- "capability": "auth",
2107
- "summary": "There is no login, sign-up or permission plugin",
2108
- "instead": "Write it in app code. Keep the login state in @dokebi/session and hash passwords with scrypt from node:crypto. Install an outside auth library only with the permission of the user",
2109
- "verify": [
2110
- "no-provider"
2111
- ]
2112
- },
2113
- {
2114
- "capability": "csrf",
2115
- "summary": "There is no CSRF token plugin",
2116
- "instead": "The app keeps a random token in the session and compares it with a hidden form field or a header (the @dokebi/session AI.md says what sameSite does and does not block)",
4410
+ "instead": "Create one more app with dokebi app add admin --domain auto (its host admin.<project>.localhost) and write its screens in app code; protect them with @dokebi/auth (config.auth on the routes)",
2117
4411
  "verify": [
2118
4412
  "no-provider"
2119
4413
  ]
@@ -2127,25 +4421,17 @@
2127
4421
  ]
2128
4422
  },
2129
4423
  {
2130
- "capability": "i18n",
2131
- "summary": "There is no i18n plugin",
2132
- "instead": "Keep an object of strings per language in app code",
2133
- "verify": [
2134
- "no-provider"
2135
- ]
2136
- },
2137
- {
2138
- "capability": "mail",
2139
- "summary": "There is no mail plugin",
2140
- "instead": "With the permission of the user, use an SMTP library in app code. Mail that must be sent goes through an @dokebi/job job",
4424
+ "capability": "markdown",
4425
+ "summary": "There is no Markdown to HTML converter (@dokebi/pdf-writer lays markdown out only in PDF files)",
4426
+ "instead": "Store and show the requested content as plain text (EJS escapes with <%= %>), or use a Markdown library with the permission of the user; to put markdown in a PDF file, use @dokebi/pdf-writer (it never makes HTML)",
2141
4427
  "verify": [
2142
4428
  "no-provider"
2143
4429
  ]
2144
4430
  },
2145
4431
  {
2146
- "capability": "markdown",
2147
- "summary": "There is no Markdown converter",
2148
- "instead": "Store and show post bodies as plain text (EJS escapes with <%= %>), or use a Markdown library with the permission of the user",
4432
+ "capability": "oidc.client",
4433
+ "summary": "There is no OpenID Connect client for an arbitrary provider (the social login of @dokebi/auth knows Google and Apple only)",
4434
+ "instead": "Write the client in app code next to @dokebi/auth (request.auth.login after the callback) with the authorization code flow, PKCE S256, state and nonce bound to the browser, the ID token checked against the provider's JWKS (issuer, audience, expiry, nonce, no alg none), and users found by issuer and subject; make the provider's issuer, client id and secret settings; ask the user before adding a library",
2149
4435
  "verify": [
2150
4436
  "no-provider"
2151
4437
  ]
@@ -2158,34 +4444,10 @@
2158
4444
  "no-provider"
2159
4445
  ]
2160
4446
  },
2161
- {
2162
- "capability": "rate-limit",
2163
- "summary": "There is no rate limit plugin",
2164
- "instead": "Limit requests in the front proxy (nginx or similar), or register a Fastify plugin with the permission of the user",
2165
- "verify": [
2166
- "no-provider"
2167
- ]
2168
- },
2169
4447
  {
2170
4448
  "capability": "rss",
2171
4449
  "summary": "There is no RSS or Atom feed generator",
2172
- "instead": "Build the XML string in a controller and answer with application/rss+xml",
2173
- "verify": [
2174
- "no-provider"
2175
- ]
2176
- },
2177
- {
2178
- "capability": "search",
2179
- "summary": "There is no full-text search plugin",
2180
- "instead": "Use LIKE or the full-text search of the database (such as postgres tsvector) in app code",
2181
- "verify": [
2182
- "no-provider"
2183
- ]
2184
- },
2185
- {
2186
- "capability": "upload",
2187
- "summary": "There is no file upload (multipart) plugin",
2188
- "instead": "With the permission of the user the app registers a Fastify multipart plugin, or it starts without uploads",
4450
+ "instead": "Build the feed in an application service, escape XML values, and have the controller answer with application/rss+xml",
2189
4451
  "verify": [
2190
4452
  "no-provider"
2191
4453
  ]