@voltro/cli 0.52.0 → 0.53.0

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 (195) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/THIRD-PARTY-NOTICES.md +8311 -3318
  3. package/dist/{apiBuild-CSFI8QGq.js → apiBuild-CaPfoWku.js} +11 -5
  4. package/dist/apiBuild-DHtLXYx9.js +2 -0
  5. package/dist/bin.js +1 -1
  6. package/dist/build-D-OnvNMf.js +843 -0
  7. package/dist/{checkCommand-COmqc2cB.js → checkCommand-C5elt0tW.js} +6 -6
  8. package/dist/{checkCommand-2SbqzukH.js → checkCommand-D2ZduVlh.js} +1 -1
  9. package/dist/codegen-BWpt3VgF.js +2 -0
  10. package/dist/{codegen-VF479Cnb.js → codegen-FEk8AZHb.js} +2 -2
  11. package/dist/{codegenCommand-DCdG2JN-.js → codegenCommand-BOiWQ5hz.js} +4 -4
  12. package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-BjtB2lq6.js} +109 -11
  13. package/dist/{commands-Cc_nV8WI.js → commands-DyxAmhP0.js} +30 -30
  14. package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-BdKTyT13.js} +5 -5
  15. package/dist/{dataCommand-BgpBHnlB.js → dataCommand-Bab9X7s8.js} +2 -2
  16. package/dist/{dbCommand-sHedr-NJ.js → dbCommand-06O2finM.js} +277 -236
  17. package/dist/dbCommand-B1EXBC6f.js +2 -0
  18. package/dist/{dev-CRHoCEiy.js → dev-C6LGF4iY.js} +3006 -2426
  19. package/dist/{dev--A3nsxA3.js → dev-GjJWAYo2.js} +1 -1
  20. package/dist/doctorCommand-B0hX0tdz.js +2 -0
  21. package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-etMkflRc.js} +101 -69
  22. package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-UwZ1AZzB.js} +1 -1
  23. package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-C70zWHwo.js} +1 -1
  24. package/dist/{envCommand-D4gCrrTZ.js → envCommand-dSyKvRkM.js} +18 -18
  25. package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-CG0_ebO5.js} +2 -2
  26. package/dist/fileConventions-DASGEmj-.js +35 -0
  27. package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-B7uxipWS.js} +55 -55
  28. package/dist/fontPipeline-LxIHa1vo.js +2 -0
  29. package/dist/fontPipeline-Tsh8kZfA.js +152 -0
  30. package/dist/frameworkTableAssembly-C_7Z-rMs.js +2 -0
  31. package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-DKx3ba3S.js} +5 -5
  32. package/dist/imagePipeline-B_GVJgm6.js +2 -0
  33. package/dist/imagePipeline-CBZmjT4i.js +127 -0
  34. package/dist/index.js +1 -1
  35. package/dist/{infoCommand-DXM868o_.js → infoCommand-_53iOc_j.js} +1 -1
  36. package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
  37. package/dist/inspect-CuoDInfZ.js +2 -0
  38. package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
  39. package/dist/manifestBuild-C4-J1-m_.js +2 -0
  40. package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
  41. package/dist/{metaCommands-C6RFmF1r.js → metaCommands-Cn2oboG4.js} +8 -2
  42. package/dist/{migrate-D0F-eTlK.js → migrate-Cko9rswM.js} +2 -2
  43. package/dist/{pageConvention-CzUiSbtU.js → pageConvention-C938S8oC.js} +1 -1
  44. package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-DWTQMC6R.js} +2 -2
  45. package/dist/{probeCommand-C9gazU0H.js → probeCommand-DkGGLknv.js} +83 -24
  46. package/dist/{renderModeScan-CcH2X1_D.js → renderModeScan-CUbOeOAg.js} +3 -1
  47. package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-CskIgAfn.js} +2 -2
  48. package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-c0APJz7E.js} +1 -1
  49. package/dist/{sdkgen-Se88ifTd.js → sdkgen-BiQCgIEr.js} +1 -1
  50. package/dist/{serveCommand-DkP3OT0W.js → serveCommand-CueKQgzl.js} +869 -805
  51. package/dist/serveCommand-DsnrVN3U.js +2 -0
  52. package/dist/serveEntry.js +1 -1
  53. package/dist/start-BJzZLbt8.js +3 -0
  54. package/dist/start-ekPan8BT.js +1510 -0
  55. package/dist/startEntry.js +1 -1
  56. package/dist/{staticCommand-BwNEDlSU.js → staticCommand-xlSL-IWk.js} +1 -1
  57. package/dist/{test-f3amja6a.js → test-BWPQcRoB.js} +1 -1
  58. package/dist/updateCommand-Bqql_rsQ.js +2 -0
  59. package/dist/{updateCommand-BMk2e4ky.js → updateCommand-C_8I8Rzo.js} +139 -115
  60. package/dist/webDev-C7jWJ5dX.js +2 -0
  61. package/dist/{webDev-BgWL9gKV.js → webDev-oczpugbx.js} +1455 -922
  62. package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-4SVPDjKg.js} +1 -1
  63. package/package.json +60 -19
  64. package/templates/AGENTS.md +5 -3
  65. package/templates/agent-docs/_index.md +5 -3
  66. package/templates/agent-docs/_manifest.json +20 -4
  67. package/templates/agent-docs/ai.md +2 -2
  68. package/templates/agent-docs/authentication.md +1 -1
  69. package/templates/agent-docs/cli.md +2 -2
  70. package/templates/agent-docs/configuration.md +17 -0
  71. package/templates/agent-docs/data.md +470 -26
  72. package/templates/agent-docs/database/advancedqueries.md +7 -7
  73. package/templates/agent-docs/database/columntypes.md +2 -2
  74. package/templates/agent-docs/database/querying.md +1 -1
  75. package/templates/agent-docs/database/schema.md +1 -1
  76. package/templates/agent-docs/database/seedsdialects.md +1 -1
  77. package/templates/agent-docs/database/transactions.md +3 -3
  78. package/templates/agent-docs/deployment.md +8 -0
  79. package/templates/agent-docs/internationalization.md +2 -2
  80. package/templates/agent-docs/introduction.md +7 -1
  81. package/templates/agent-docs/local-first-mobile.md +102 -4
  82. package/templates/agent-docs/observability.md +2 -0
  83. package/templates/agent-docs/plugins/auth.md +1 -1
  84. package/templates/agent-docs/plugins/comments.md +142 -0
  85. package/templates/agent-docs/plugins/notifications.md +47 -4
  86. package/templates/agent-docs/plugins/presence.md +16 -3
  87. package/templates/agent-docs/plugins/prometheus.md +1 -1
  88. package/templates/agent-docs/plugins/queue.md +129 -0
  89. package/templates/agent-docs/plugins.md +11 -7
  90. package/templates/agent-docs/reference.md +16 -4
  91. package/templates/agent-docs/routing.md +527 -3
  92. package/templates/agent-docs/schema-driven-ui.md +214 -3
  93. package/templates/agent-docs/security.md +3 -3
  94. package/templates/agent-docs/whats-new.md +160 -66
  95. package/templates/apps/api-ai/package.json +6 -6
  96. package/templates/apps/api-auth/package.json +8 -8
  97. package/templates/apps/api-backend/package.json +7 -7
  98. package/templates/apps/api-backend-deactivation/package.json +7 -7
  99. package/templates/apps/api-backend-mail/package.json +8 -8
  100. package/templates/apps/api-backend-mariadb/package.json +9 -9
  101. package/templates/apps/api-backend-sqlite/package.json +8 -8
  102. package/templates/apps/api-backend-storage/package.json +8 -8
  103. package/templates/apps/api-cms/package.json +9 -9
  104. package/templates/apps/api-collab/package.json +8 -8
  105. package/templates/apps/api-data-advanced/package.json +8 -8
  106. package/templates/apps/api-durable/package.json +8 -8
  107. package/templates/apps/api-feature-flags/package.json +9 -9
  108. package/templates/apps/api-governance/package.json +8 -8
  109. package/templates/apps/api-kv/package.json +8 -8
  110. package/templates/apps/api-moderation/package.json +8 -8
  111. package/templates/apps/api-observability/package.json +8 -8
  112. package/templates/apps/api-ratelimit/package.json +8 -8
  113. package/templates/apps/api-rbac/package.json +8 -8
  114. package/templates/apps/api-rest/package.json +7 -7
  115. package/templates/apps/api-row-history/package.json +8 -8
  116. package/templates/apps/api-saas/package.json +10 -10
  117. package/templates/apps/api-saas-starter/package.json +10 -10
  118. package/templates/apps/api-search/package.json +8 -8
  119. package/templates/apps/api-status/package.json +8 -8
  120. package/templates/apps/api-webhooks/package.json +9 -9
  121. package/templates/apps/changelog/app.config.ts +26 -2
  122. package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
  123. package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
  124. package/templates/apps/changelog/package.json +8 -8
  125. package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
  126. package/templates/apps/changelog/src/globals.d.ts +1 -1
  127. package/templates/apps/changelog/src/locales/de.ts +1 -1
  128. package/templates/apps/changelog/src/locales/en.ts +1 -1
  129. package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
  130. package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
  131. package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
  132. package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
  133. package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
  134. package/templates/apps/changelog/src/pages/page.tsx +18 -12
  135. package/templates/apps/edge-functions/package.json +2 -2
  136. package/templates/apps/frontend-admin/package.json +8 -8
  137. package/templates/apps/frontend-app/package.json +9 -9
  138. package/templates/apps/frontend-auth/package.json +8 -8
  139. package/templates/apps/frontend-blank/package.json +7 -7
  140. package/templates/apps/frontend-cms/package.json +9 -9
  141. package/templates/apps/frontend-collab/package.json +10 -10
  142. package/templates/apps/frontend-contact/package.json +7 -7
  143. package/templates/apps/frontend-dashboard/package.json +7 -7
  144. package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
  145. package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
  146. package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
  147. package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
  148. package/templates/apps/frontend-docs/package.json +8 -6
  149. package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
  150. package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
  151. package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
  152. package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
  153. package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
  154. package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
  155. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
  156. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
  157. package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
  158. package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
  159. package/templates/apps/frontend-i18n/package.json +6 -6
  160. package/templates/apps/frontend-landing/package.json +6 -6
  161. package/templates/apps/frontend-portal/package.json +8 -8
  162. package/templates/apps/frontend-saas/package.json +8 -8
  163. package/templates/apps/frontend-spa/package.json +7 -7
  164. package/templates/apps/frontend-ssr/package.json +7 -7
  165. package/templates/apps/frontend-ssr-api/package.json +8 -8
  166. package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
  167. package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
  168. package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
  169. package/templates/apps/frontend-static-blog/package.json +8 -6
  170. package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
  171. package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
  172. package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
  173. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
  174. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
  175. package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
  176. package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
  177. package/templates/apps/frontend-status/package.json +8 -8
  178. package/templates/apps/mobile-app/package.json +4 -4
  179. package/dist/apiBuild-BYBpL7Pz.js +0 -2
  180. package/dist/build-CPgcMQug.js +0 -793
  181. package/dist/codegen-CctkDO-1.js +0 -2
  182. package/dist/dbCommand-DNb6yeOG.js +0 -2
  183. package/dist/doctorCommand-CqoWA2p5.js +0 -2
  184. package/dist/fileConventions-DOqD3lPS.js +0 -34
  185. package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
  186. package/dist/inspect-CuGDYES0.js +0 -2
  187. package/dist/manifestBuild-CPjhvM62.js +0 -2
  188. package/dist/serveCommand-DLc-BznW.js +0 -2
  189. package/dist/start-DfL3fOiN.js +0 -3
  190. package/dist/start-jw89Xbqy.js +0 -1339
  191. package/dist/updateCommand-5gFVfK5q.js +0 -2
  192. package/dist/webDev-CZbTsDcH.js +0 -2
  193. package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
  194. package/templates/apps/changelog/src/lib/releases.ts +0 -21
  195. package/templates/apps/frontend-static-blog/src/content/posts.ts +0 -64
@@ -0,0 +1,129 @@
1
+ # Queue (Kafka interop)
2
+
3
+ > Consume and produce against an existing Kafka — Schema-decoded consumers (at-least-once, serial per partition, retry + dead-letter), batched producing via a handler service or transactionally through the outbox, and a kafkaSink for cdc-out.
4
+
5
+
6
+
7
+ ---
8
+
9
+ <!-- source: en/plugins/queue.md -->
10
+ ## Queue (Kafka interop)
11
+
12
+ _Consume and produce against an existing Kafka — Schema-decoded consumers (at-least-once, serial per partition, retry + dead-letter), batched producing via a handler service or transactionally through the outbox, and a kafkaSink for cdc-out._
13
+
14
+ `@voltro/plugin-queue` is the door to queues **somebody else owns**: a Voltro
15
+ backend consuming and producing against an adopter's existing Kafka. The
16
+ boundary with the built-ins, in one line each: the [outbox](/docs/data/outbox)
17
+ is *your own* durable side-effects, a [workflow](/docs/workflows/overview) is
18
+ *your own* orchestration — this plugin is interop with foreign infrastructure.
19
+ Kafka first; the provider contract is cut so SQS/RabbitMQ can be later
20
+ implementations.
21
+
22
+ ```ts
23
+ // app.config.ts
24
+ import { queuePlugin } from '@voltro/plugin-queue'
25
+
26
+ export default {
27
+ // …
28
+ plugins: [
29
+ queuePlugin({ brokers: ['kafka-1:9092', 'kafka-2:9092'] }),
30
+ ],
31
+ }
32
+ ```
33
+
34
+ ## Consuming: `*.consumer.ts`
35
+
36
+ ```ts
37
+ // src/consumers/orders.consumer.ts
38
+ import { Schema } from 'effect'
39
+ import { defineQueueConsumer } from '@voltro/plugin-queue'
40
+
41
+ export const orders = defineQueueConsumer({
42
+ topic: 'orders',
43
+ schema: Schema.Struct({ orderId: Schema.String, total: Schema.Number }),
44
+ handler: async (order, ctx) => {
45
+ // MUST be idempotent — delivery is at-least-once. A unique-column
46
+ // upsert is the standard shape:
47
+ await ctx.store.upsert('orders_mirror',
48
+ { orderId: order.orderId, total: order.total },
49
+ { conflictColumns: ['orderId'] })
50
+ },
51
+ })
52
+ ```
53
+
54
+ Both boot paths discover `*.consumer.ts`; the plugin starts every registered
55
+ consumer at activation and stops them at shutdown. The semantics, precisely:
56
+
57
+ - **Ordering: serial per partition.** Parallelism exists only ACROSS
58
+ partitions — concurrency inside one would destroy ordering and commit
59
+ semantics both. Retry backoff deliberately BLOCKS the partition.
60
+ - **Commit after the handler, per message.** A process killed mid-batch
61
+ redelivers exactly the unhandled tail — never the whole batch, never a
62
+ skipped message.
63
+ - **Decode failures dead-letter IMMEDIATELY** (to `<topic>.dlq`, with
64
+ `x-voltro-dlq-*` reason headers) — a deterministic failure retried forever
65
+ is an infinite loop with extra steps, and a poison message must release
66
+ its partition.
67
+ - **Handler failures retry with backoff, then dead-letter** after
68
+ `maxAttempts` (default 3).
69
+ - **A rebalance is not a failure.** A partition revoked mid-batch or
70
+ mid-retry stops processing without a retry-counter increment or a DLQ
71
+ publish — the new owner redelivers.
72
+ - **Handlers are NOT wrapped in a transaction** (the same documented
73
+ boundary as HTTP route handlers). A handler needing atomic multi-writes
74
+ opens `ctx.store.transactional` itself — and stays idempotent either way.
75
+ - **Replica coordination is Kafka's own.** Every replica joins the same
76
+ consumer group and the broker assigns partitions — no advisory lock, unlike
77
+ [schedules](/docs/scheduling/coordination), which coordinate through the
78
+ claim table because no broker exists to do it for them.
79
+
80
+ ## Producing
81
+
82
+ Two paths, one rule: transactional-with-a-write goes through the outbox.
83
+
84
+ ```ts
85
+ // Inside a mutation — commits or rolls back WITH the domain write:
86
+ await ctx.outbox.enqueue('queue.produce', {
87
+ topic: 'orders',
88
+ messages: [{ key: order.id, value: JSON.stringify(order) }],
89
+ })
90
+ ```
91
+
92
+ ```ts
93
+ // src/queue.outbox.ts — the bridge (once per app):
94
+ import { queueOutboxHandler } from '@voltro/plugin-queue'
95
+ export default queueOutboxHandler()
96
+ ```
97
+
98
+ The outbox runner delivers after commit — batched (`messages` is an array →
99
+ one transport round-trip), at-least-once, retried, dead-lettered. One
100
+ durability path: the existing outbox, not a second one. For fire-and-forget
101
+ producing without a surrounding write, `yield* QueueService` in a handler and
102
+ call `produce(topic, messages)` directly.
103
+
104
+ Topic creation is EXPLICIT (`provider.ensureTopics([...])`) — your Kafka is
105
+ foreign infrastructure, and whether a client may create topics is your
106
+ policy. A consumer started against a topic that does not exist yet warns and
107
+ retries in the background (it connects once the topic appears), never
108
+ aborting the boot.
109
+
110
+ ## cdc-out to Kafka
111
+
112
+ `kafkaSink` plugs table-change mirroring ([plugin-cdc-out](/docs/plugins/cdc-out))
113
+ into the SAME provider: message key = the row id (one row's changes stay
114
+ ordered in one partition), value = the change record, and the
115
+ `x-voltro-delivery-key` header carries cdc-out's at-least-once dedupe handle.
116
+
117
+ ```ts
118
+ import { cdcOutPlugin } from '@voltro/plugin-cdc-out'
119
+ import { kafkaSink } from '@voltro/plugin-queue'
120
+
121
+ cdcOutPlugin({ sinks: [{ table: 'orders', sink: kafkaSink({ topic: 'orders.cdc' }) }] })
122
+ ```
123
+
124
+ ## Observability
125
+
126
+ Per-topic counters (consumed / retried / dead-lettered / produced + the last
127
+ error) on `GET /_voltro/inspect/plugins/queue/consumers` and in the
128
+ dashboards' Queue panel. Message headers carry `traceparent` through to
129
+ `ctx.traceparent` for cross-system trace continuity.
@@ -42,7 +42,9 @@ The framework ships some plugins; you write your own; the contract is small enou
42
42
  - [plugin-logship](/docs/plugins/logship) — ship structured logs to Better Stack / Axiom / Loki / any HTTP sink; batched, redacted, fail-soft
43
43
  - [plugin-moderation](/docs/plugins/moderation) — moderate user content before commit: keyword or AI provider, block / flag via interceptor + in-handler redact
44
44
  - [plugin-search](/docs/plugins/search) — keep an external index (Typesense / Meilisearch / Algolia) in sync via the ChangeEvent tap; tenant-scoped `search.query` + hook
45
- - [plugin-cdc-out](/docs/plugins/cdc-out) — declarative reverse-ETL: mirror table changes outward to a webhook sink (or any custom `CdcSink`) through a durable outbox; ordered per pipe, at-least-once from enqueue, dead-lettered
45
+ - [plugin-cdc-out](/docs/plugins/cdc-out) — declarative reverse-ETL: mirror table changes outward to a webhook or Kafka sink (or any custom `CdcSink`) through a durable outbox; ordered per pipe, at-least-once from enqueue, dead-lettered
46
+ - [plugin-queue](/docs/plugins/queue) — Kafka interop: Schema-decoded consumers (`*.consumer.ts`; at-least-once, serial per partition, retry + dead-letter), batched producing via a service or transactionally through the outbox, and a `kafkaSink` for cdc-out
47
+ - [plugin-comments](/docs/plugins/comments) — comment threads on any app entity: replies, resolve/reopen, tenant-safe @-mentions with notifications, reactions, unread — live over the reactive engine, with the ejectable `<CommentsThread>` UI
46
48
  - [plugin-governance](/docs/plugins/governance) — data governance: retention TTL sweep, GDPR export + erasure, consent ledger, field encryption
47
49
  - [plugin-openapi](/docs/plugins/openapi) — OpenAPI 3.1 spec + Swagger-UI docs generated from your `defineRestRoute` descriptors and (opt-in) rpc procedures
48
50
  - [plugin-row-history](/docs/plugins/row-history) — full row history + time-travel (`rowHistory` / `rowAsOf` / `restoreAsOf` / `diffVersions`); what-changed-to-what on every write
@@ -74,13 +76,15 @@ Status legend: ✓ shipped · ◐ partial · — planned.
74
76
  | `@voltro/plugin-postgis` | ✓ | Postgres-native `geography` / `geometry` columns + spatial predicates (`ST_DWithin`, `ST_Contains`, `ST_Intersects`); GiST indexes via `.expressionIndex(..., { kind: 'gist' })`. No `ST_Distance` projection yet. Postgres-only by design (fails loud elsewhere). [→ details](/docs/plugins/postgis) |
75
77
  | `@voltro/plugin-broadcast` | ✓ | Cross-replica reactivity — fans out app-mutation change events to every replica over a pub/sub bus (Redis / NATS). Closes the single-instance gap for every non-postgres dialect. [→ details](/docs/plugins/broadcast) |
76
78
  | `@voltro/plugin-webhooks` | ✓ | Incoming + outgoing webhooks — `defineIncomingWebhook` (signature verify + idempotency, Stripe/GitHub/Slack presets) and `defineEvent` (durable delivery workflow, HMAC signing, retries, filters). [→ details](/docs/plugins/webhooks) |
79
+ | `@voltro/plugin-comments` | ✓ | Comment threads anchored to any entity — fail-closed access delegation (`viaEntity`/`scope`), replies, resolve/reopen, tenant-safe mentions (validated twice, delivered via plugin-notifications incl. digests), reactions, per-subject unread, live `comments.list`, `<CommentsThread>` in `@voltro/ui`. [→ details](/docs/plugins/comments) |
80
+ | `@voltro/plugin-queue` | ✓ | Kafka interop — `defineQueueConsumer` (`*.consumer.ts`, Schema-decoded, at-least-once, serial per partition, retry + DLQ with reason headers), batched producing via `QueueService` or transactionally through the outbox (`queueOutboxHandler`), `kafkaSink` for cdc-out, per-topic counters in the dashboards. [→ details](/docs/plugins/queue) |
77
81
  | `@voltro/plugin-auth-social` | ✓ | First-party social login — Sign in with Google / GitHub / Apple with no identity vendor: authorize URL + code exchange + JWKS-verified ID tokens, mandatory PKCE (S256) and `state`, an explicit account-linking policy (`never` by default), Apple's signed-JWT client secret / one-time name / private-relay email all handled; sessions via `issueUserSession`. [→ details](/docs/plugins/auth-social) |
78
82
  | `@voltro/plugin-auth-{workos,kinde,clerk,auth0,supabase,oidc}` | ✓ | Six IdP adapters over the shared `jwtBearerStrategy` — JWKS verify + claims→tenant mapping; WorkOS additionally ships hosted-login OAuth primitives (`workosAuthorizationUrl` / `workosAuthenticateWithCode`) for a redirect-based SSO login flow. [→ details](/docs/authentication/external-idp) |
79
- | `@voltro/plugin-analytics-postgres` | ✓ | First-party lite — events on the main DataStore, cross-dialect (postgres / mysql / mariadb / mssql / sqlite / turso). [→ details](/docs/plugins/analytics#voltroplugin-analytics-postgres) |
80
- | `@voltro/plugin-duckdb` | ✓ | Embedded DuckDB sidecar — real OLAP performance, no external service. [→ details](/docs/plugins/analytics#voltroplugin-duckdb) |
81
- | `@voltro/plugin-clickhouse` | ✓ | Production OLAP via the official ClickHouse client. [→ details](/docs/plugins/analytics#voltroplugin-clickhouse) |
82
- | `@voltro/plugin-tinybird` | ✓ | Hosted ClickHouse via Events API + Pipes. [→ details](/docs/plugins/analytics#voltroplugin-tinybird) |
83
- | `@voltro/plugin-posthog` | ✓ | Product analytics — track-only; compose with another sink for reads. [→ details](/docs/plugins/analytics#voltroplugin-posthog) |
83
+ | `@voltro/plugin-analytics-postgres` | ✓ | First-party lite — events on the main DataStore, cross-dialect (postgres / mysql / mariadb / mssql / sqlite / turso). [→ details](/docs/plugins/analytics) |
84
+ | `@voltro/plugin-duckdb` | ✓ | Embedded DuckDB sidecar — real OLAP performance, no external service. [→ details](/docs/plugins/analytics) |
85
+ | `@voltro/plugin-clickhouse` | ✓ | Production OLAP via the official ClickHouse client. [→ details](/docs/plugins/analytics) |
86
+ | `@voltro/plugin-tinybird` | ✓ | Hosted ClickHouse via Events API + Pipes. [→ details](/docs/plugins/analytics) |
87
+ | `@voltro/plugin-posthog` | ✓ | Product analytics — track-only; compose with another sink for reads. [→ details](/docs/plugins/analytics) |
84
88
  | `@voltro/plugin-atlassian` | ✓ | `JiraService` + `ConfluenceService` over the Atlassian REST / Greenhopper / Agile APIs — PAT **or** OAuth 2.0 (3LO) auth, transient retry, SSRF guard, comment-write, signature-verified inbound webhooks, avatar proxy, per-tenant cache. [→ details](/docs/plugins/atlassian) |
85
89
  | `@voltro/plugin-deactivation` | ✓ | `deactivation()` schema mixin — `deactivatedAt` + `deactivatedBy` (→ Actor); subject can't log in but data stays visible. [→ details](/docs/plugins/deactivation) |
86
90
  | `@voltro/plugin-prometheus` | ✓ | Prometheus exporter — `GET /metrics` in text exposition format over the unified Metrics-API (Effect `MetricRegistry`); counters / histograms / gauges + custom metrics, optional bearer gate + node process metrics. [→ details](/docs/plugins/prometheus) |
@@ -880,7 +884,7 @@ A plugin can mount plain HTTP routes beside the rpc surface (`@voltro/plugin-sto
880
884
 
881
885
  - **The full method union is first-class.** `method` is `'*' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'`. **HEAD is admitted wherever GET is** (RFC 9110) — the GET handler runs and the transport drops the body; you never mount a second route for it. A wrong method stays a precise `405` with an `Allow:` header, including when several routes share one path.
882
886
  - **The body read is capped** — 8 MiB by default, the same cap as every other surface (`http.maxBodyBytes` in `app.config.ts`, env `VOLTRO_MAX_BODY_BYTES`), and the read is binary-clean. A route that takes more declares its own `maxBodyBytes`; routes **sharing a path share one body read**, so the widest override in the group applies to the group. Oversize answers `413` for both `Content-Length` and chunked requests.
883
- - **Binary streaming responses** return `byteStream` on the `PluginHttpRouteResult` — a `ReadableStream<Uint8Array>` (or lazy thunk) with optional `contentLength` / `contentDisposition`, piped without buffering and never compressed. It is the plugin-route spelling of the REST surface's [`bytes()`](/docs/data/rest-routes#binary-downloads--bytes).
887
+ - **Binary streaming responses** return `byteStream` on the `PluginHttpRouteResult` — a `ReadableStream<Uint8Array>` (or lazy thunk) with optional `contentLength` / `contentDisposition`, piped without buffering and never compressed. It is the plugin-route spelling of the REST surface's [`bytes()`](/docs/data/rest-routes#binary-downloads-bytes).
884
888
  - **Buffered responses are compression-negotiated** (brotli/gzip, compressible types only) by the listener — nothing to declare; see [Security → compression](/docs/security/overview).
885
889
 
886
890
  A state-changing plugin route is origin-checked unless it declares `originGuard: 'exempt'`, and `req.remoteAddr` is the trusted-proxy-resolved client address — both covered with examples in [Security](/docs/security/overview#routes-that-a-third-party-legitimately-posts-to).
@@ -225,6 +225,18 @@ const { data } = useSubscription(
225
225
  )
226
226
  ```
227
227
 
228
+ ### Offline semantics with the local-first mirror
229
+
230
+ With `@voltro/local-first`'s query mirror bound (see
231
+ [the sync engine](/docs/local-first/overview#the-sync-engine-query-mirror--durable-outbox)),
232
+ `useSubscription`'s behaviour extends offline WITHOUT a second API: a cold
233
+ start seeds `data` (and `revision`) from the device's mirrored rows for the
234
+ subject's partition, so `loading` resolves against local data when the server
235
+ is unreachable; the first live event replaces it, and a reconnect inside the
236
+ resume window continues with deltas from the mirrored revision. Offline
237
+ WRITES ride [`useOutbox`](/docs/ui/client-utilities/use-outbox) — durable
238
+ with `outboxPersistence()`, conflict resolution via `resolveConflict`.
239
+
228
240
  ## `useMutation(apiName, rpcTag)`
229
241
 
230
242
  Calls a `defineMutation` RPC.
@@ -592,7 +604,7 @@ window.history.forward() // forward
592
604
 
593
605
  ## `useBlocker()`
594
606
 
595
- Hold a pending navigation so you can prompt before the user leaves — the unsaved-changes guard.
607
+ Hold a pending navigation so you can prompt before the user leaves — the unsaved-changes guard. It guards SPA navigations, the browser's **Back/Forward gestures** (popstate: the router reverts the already-moved URL and offers `retry`/`reset` — this also covers ESC inside an [intercepting-route overlay](/docs/routing/intercepting-routes)), and full-page unloads via `beforeunload`.
596
608
 
597
609
  ```tsx
598
610
  import { useBlocker } from '@voltro/web'
@@ -637,7 +649,7 @@ setParams((p) => { p.set('page', '2'); return p }) // patch one param
637
649
  setParams({ page: '2' }, { push: true }) // distinct history entry
638
650
  ```
639
651
 
640
- Writes default to a history replace; pass `{ push: true }` for a Back entry or `{ scroll: false }` to keep scroll. See [Navigation](/docs/routing/navigation#reading--writing-search-params).
652
+ Writes default to a history replace; pass `{ push: true }` for a Back entry or `{ scroll: false }` to keep scroll. See [Navigation](/docs/routing/navigation#reading-writing-search-params).
641
653
 
642
654
  `useSetSearchParams(searchParams)` — pass the schema to get the **typed** setter. Object form replaces the query (a left-out field decodes to its default on the next read); the updater form receives the current **decoded** params, so a merge is an explicit spread:
643
655
 
@@ -715,7 +727,7 @@ Precisely, it returns `LoaderData<T>`. For every ordinary loader that IS `T`. Fo
715
727
  a loader that returned `defer()`, `LoaderData<T>` flattens the two buckets into
716
728
  one object — eager fields as values, deferred fields as `Promise<T>` — so the
717
729
  compiler tells you which fields have to be rendered through
718
- [`<Await>`](/docs/routing/loaders-and-meta#deferring-slow-data-defer--await):
730
+ [`<Await>`](/docs/routing/loaders-and-meta#deferring-slow-data-defer):
719
731
 
720
732
  ```tsx
721
733
  export const loader = async ({ query }) => defer(
@@ -856,7 +868,7 @@ that must react to router-pushed query changes without a reload re-render throug
856
868
 
857
869
  Prefer the typed form where the page declares a `searchParams` schema export —
858
870
  `useSearchParams(searchParams)` returns the decoded shape instead of a raw
859
- `URLSearchParams`. See [Routing hooks](/docs/reference/hooks-routing#usesearchparams--usesetsearchparams).
871
+ `URLSearchParams`. See [Routing hooks](/docs/reference/hooks-routing#usesearchparams-usesetsearchparams).
860
872
 
861
873
  ## Reading cookies
862
874