@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.
- package/CHANGELOG.md +229 -0
- package/THIRD-PARTY-NOTICES.md +8311 -3318
- package/dist/{apiBuild-CSFI8QGq.js → apiBuild-CaPfoWku.js} +11 -5
- package/dist/apiBuild-DHtLXYx9.js +2 -0
- package/dist/bin.js +1 -1
- package/dist/build-D-OnvNMf.js +843 -0
- package/dist/{checkCommand-COmqc2cB.js → checkCommand-C5elt0tW.js} +6 -6
- package/dist/{checkCommand-2SbqzukH.js → checkCommand-D2ZduVlh.js} +1 -1
- package/dist/codegen-BWpt3VgF.js +2 -0
- package/dist/{codegen-VF479Cnb.js → codegen-FEk8AZHb.js} +2 -2
- package/dist/{codegenCommand-DCdG2JN-.js → codegenCommand-BOiWQ5hz.js} +4 -4
- package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-BjtB2lq6.js} +109 -11
- package/dist/{commands-Cc_nV8WI.js → commands-DyxAmhP0.js} +30 -30
- package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-BdKTyT13.js} +5 -5
- package/dist/{dataCommand-BgpBHnlB.js → dataCommand-Bab9X7s8.js} +2 -2
- package/dist/{dbCommand-sHedr-NJ.js → dbCommand-06O2finM.js} +277 -236
- package/dist/dbCommand-B1EXBC6f.js +2 -0
- package/dist/{dev-CRHoCEiy.js → dev-C6LGF4iY.js} +3006 -2426
- package/dist/{dev--A3nsxA3.js → dev-GjJWAYo2.js} +1 -1
- package/dist/doctorCommand-B0hX0tdz.js +2 -0
- package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-etMkflRc.js} +101 -69
- package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-UwZ1AZzB.js} +1 -1
- package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-C70zWHwo.js} +1 -1
- package/dist/{envCommand-D4gCrrTZ.js → envCommand-dSyKvRkM.js} +18 -18
- package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-CG0_ebO5.js} +2 -2
- package/dist/fileConventions-DASGEmj-.js +35 -0
- package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-B7uxipWS.js} +55 -55
- package/dist/fontPipeline-LxIHa1vo.js +2 -0
- package/dist/fontPipeline-Tsh8kZfA.js +152 -0
- package/dist/frameworkTableAssembly-C_7Z-rMs.js +2 -0
- package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-DKx3ba3S.js} +5 -5
- package/dist/imagePipeline-B_GVJgm6.js +2 -0
- package/dist/imagePipeline-CBZmjT4i.js +127 -0
- package/dist/index.js +1 -1
- package/dist/{infoCommand-DXM868o_.js → infoCommand-_53iOc_j.js} +1 -1
- package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
- package/dist/inspect-CuoDInfZ.js +2 -0
- package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
- package/dist/manifestBuild-C4-J1-m_.js +2 -0
- package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
- package/dist/{metaCommands-C6RFmF1r.js → metaCommands-Cn2oboG4.js} +8 -2
- package/dist/{migrate-D0F-eTlK.js → migrate-Cko9rswM.js} +2 -2
- package/dist/{pageConvention-CzUiSbtU.js → pageConvention-C938S8oC.js} +1 -1
- package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-DWTQMC6R.js} +2 -2
- package/dist/{probeCommand-C9gazU0H.js → probeCommand-DkGGLknv.js} +83 -24
- package/dist/{renderModeScan-CcH2X1_D.js → renderModeScan-CUbOeOAg.js} +3 -1
- package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-CskIgAfn.js} +2 -2
- package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-c0APJz7E.js} +1 -1
- package/dist/{sdkgen-Se88ifTd.js → sdkgen-BiQCgIEr.js} +1 -1
- package/dist/{serveCommand-DkP3OT0W.js → serveCommand-CueKQgzl.js} +869 -805
- package/dist/serveCommand-DsnrVN3U.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/start-BJzZLbt8.js +3 -0
- package/dist/start-ekPan8BT.js +1510 -0
- package/dist/startEntry.js +1 -1
- package/dist/{staticCommand-BwNEDlSU.js → staticCommand-xlSL-IWk.js} +1 -1
- package/dist/{test-f3amja6a.js → test-BWPQcRoB.js} +1 -1
- package/dist/updateCommand-Bqql_rsQ.js +2 -0
- package/dist/{updateCommand-BMk2e4ky.js → updateCommand-C_8I8Rzo.js} +139 -115
- package/dist/webDev-C7jWJ5dX.js +2 -0
- package/dist/{webDev-BgWL9gKV.js → webDev-oczpugbx.js} +1455 -922
- package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-4SVPDjKg.js} +1 -1
- package/package.json +60 -19
- package/templates/AGENTS.md +5 -3
- package/templates/agent-docs/_index.md +5 -3
- package/templates/agent-docs/_manifest.json +20 -4
- package/templates/agent-docs/ai.md +2 -2
- package/templates/agent-docs/authentication.md +1 -1
- package/templates/agent-docs/cli.md +2 -2
- package/templates/agent-docs/configuration.md +17 -0
- package/templates/agent-docs/data.md +470 -26
- package/templates/agent-docs/database/advancedqueries.md +7 -7
- package/templates/agent-docs/database/columntypes.md +2 -2
- package/templates/agent-docs/database/querying.md +1 -1
- package/templates/agent-docs/database/schema.md +1 -1
- package/templates/agent-docs/database/seedsdialects.md +1 -1
- package/templates/agent-docs/database/transactions.md +3 -3
- package/templates/agent-docs/deployment.md +8 -0
- package/templates/agent-docs/internationalization.md +2 -2
- package/templates/agent-docs/introduction.md +7 -1
- package/templates/agent-docs/local-first-mobile.md +102 -4
- package/templates/agent-docs/observability.md +2 -0
- package/templates/agent-docs/plugins/auth.md +1 -1
- package/templates/agent-docs/plugins/comments.md +142 -0
- package/templates/agent-docs/plugins/notifications.md +47 -4
- package/templates/agent-docs/plugins/presence.md +16 -3
- package/templates/agent-docs/plugins/prometheus.md +1 -1
- package/templates/agent-docs/plugins/queue.md +129 -0
- package/templates/agent-docs/plugins.md +11 -7
- package/templates/agent-docs/reference.md +16 -4
- package/templates/agent-docs/routing.md +527 -3
- package/templates/agent-docs/schema-driven-ui.md +214 -3
- package/templates/agent-docs/security.md +3 -3
- package/templates/agent-docs/whats-new.md +160 -66
- package/templates/apps/api-ai/package.json +6 -6
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +10 -10
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/app.config.ts +26 -2
- package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
- package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
- package/templates/apps/changelog/package.json +8 -8
- package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
- package/templates/apps/changelog/src/globals.d.ts +1 -1
- package/templates/apps/changelog/src/locales/de.ts +1 -1
- package/templates/apps/changelog/src/locales/en.ts +1 -1
- package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
- package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
- package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
- package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
- package/templates/apps/changelog/src/pages/page.tsx +18 -12
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +10 -10
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
- package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
- package/templates/apps/frontend-docs/package.json +8 -6
- package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
- package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
- package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
- package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
- package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
- package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +6 -6
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
- package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
- package/templates/apps/frontend-static-blog/package.json +8 -6
- package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
- package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
- package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +4 -4
- package/dist/apiBuild-BYBpL7Pz.js +0 -2
- package/dist/build-CPgcMQug.js +0 -793
- package/dist/codegen-CctkDO-1.js +0 -2
- package/dist/dbCommand-DNb6yeOG.js +0 -2
- package/dist/doctorCommand-CqoWA2p5.js +0 -2
- package/dist/fileConventions-DOqD3lPS.js +0 -34
- package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
- package/dist/inspect-CuGDYES0.js +0 -2
- package/dist/manifestBuild-CPjhvM62.js +0 -2
- package/dist/serveCommand-DLc-BznW.js +0 -2
- package/dist/start-DfL3fOiN.js +0 -3
- package/dist/start-jw89Xbqy.js +0 -1339
- package/dist/updateCommand-5gFVfK5q.js +0 -2
- package/dist/webDev-CZbTsDcH.js +0 -2
- package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
- package/templates/apps/changelog/src/lib/releases.ts +0 -21
- 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
|
|
80
|
-
| `@voltro/plugin-duckdb` | ✓ | Embedded DuckDB sidecar — real OLAP performance, no external service. [→ details](/docs/plugins/analytics
|
|
81
|
-
| `@voltro/plugin-clickhouse` | ✓ | Production OLAP via the official ClickHouse client. [→ details](/docs/plugins/analytics
|
|
82
|
-
| `@voltro/plugin-tinybird` | ✓ | Hosted ClickHouse via Events API + Pipes. [→ details](/docs/plugins/analytics
|
|
83
|
-
| `@voltro/plugin-posthog` | ✓ | Product analytics — track-only; compose with another sink for reads. [→ details](/docs/plugins/analytics
|
|
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
|
|
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
|
|
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
|
|
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
|
|
871
|
+
`URLSearchParams`. See [Routing hooks](/docs/reference/hooks-routing#usesearchparams-usesetsearchparams).
|
|
860
872
|
|
|
861
873
|
## Reading cookies
|
|
862
874
|
|