prosody 0.5.1-x86_64-linux → 0.6.0-x86_64-linux
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.
- checksums.yaml +4 -4
- data/.cargo/config.toml +3 -3
- data/.config/rail.toml +26 -0
- data/.release-please-manifest.json +1 -1
- data/.ruby-version +1 -1
- data/.taplo.toml +1 -1
- data/AGENTS.md +29 -15
- data/CHANGELOG.md +7 -0
- data/CLAUDE.md +29 -15
- data/CONFIGURATION.md +19 -16
- data/README.md +100 -23
- data/examples/keyed_state.rb +10 -2
- data/examples/keyed_state.rbs +1 -0
- data/lib/prosody/3.2/prosody.so +2 -2
- data/lib/prosody/3.3/prosody.so +2 -2
- data/lib/prosody/3.4/prosody.so +2 -2
- data/lib/prosody/client.rb +32 -0
- data/lib/prosody/configuration.rb +20 -12
- data/lib/prosody/demand.rb +27 -0
- data/lib/prosody/native_stubs/client.rb +157 -0
- data/lib/prosody/native_stubs/context.rb +178 -0
- data/lib/prosody/native_stubs/message.rb +133 -0
- data/lib/prosody/native_stubs.rb +11 -956
- data/lib/prosody/state/deque.rb +253 -0
- data/lib/prosody/state/map.rb +313 -0
- data/lib/prosody/state/set.rb +121 -0
- data/lib/prosody/state/value.rb +58 -0
- data/lib/prosody/state.rb +157 -677
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +2 -1
- data/sig/configuration.rbs +19 -10
- data/sig/prosody.rbs +37 -4
- data/sig/published.rbs +102 -0
- data/sig/state.rbs +109 -122
- data/typecheck/payload_types.rb +12 -0
- data/typecheck/payload_types.rbs +1 -0
- metadata +19 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 270bd58a5f0184bfe26ef85b0a12442f58bc95c036d09f0b2caa5e117dc06c9c
|
|
4
|
+
data.tar.gz: ec405fbe428a3c282fbbb619643cf477a15f7b095c516253495d1c896d7ca653
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70fc1084d9eaf1edeb3c202c8d973a3a1da426453a290047317b14751852cb7eacbaa6125ff831c260b6dc28d527eb4454c96166227692b537c32838cf7cf2de
|
|
7
|
+
data.tar.gz: a504e6950613bd803b71882c363311844f49d616f8cb6d8a188dd37eaf6d6d1f25699d10dc12870901f20274f82e9172acef61b4931d83e263d2bab4bf801306
|
data/.cargo/config.toml
CHANGED
|
@@ -6,9 +6,9 @@ rustflags = ["-C", "target-cpu=x86-64-v3"]
|
|
|
6
6
|
[source.crates-io]
|
|
7
7
|
replace-with = "vendored-sources"
|
|
8
8
|
|
|
9
|
-
[source."git+https://github.com/
|
|
10
|
-
git = "https://github.com/
|
|
11
|
-
rev = "
|
|
9
|
+
[source."git+https://github.com/hadronzoo/serde-magnus?rev=27aee737829a6bca399289b6e64ce4bcc08bfd94"]
|
|
10
|
+
git = "https://github.com/hadronzoo/serde-magnus"
|
|
11
|
+
rev = "27aee737829a6bca399289b6e64ce4bcc08bfd94"
|
|
12
12
|
replace-with = "vendored-sources"
|
|
13
13
|
|
|
14
14
|
[source.vendored-sources]
|
data/.config/rail.toml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[plan.work.ci]
|
|
2
|
+
paths = [".config/rail.toml", ".github/**", ".taplo.toml", "Makefile"]
|
|
3
|
+
scope = "repository"
|
|
4
|
+
|
|
5
|
+
[plan.work.ruby]
|
|
6
|
+
paths = [
|
|
7
|
+
".rspec",
|
|
8
|
+
".ruby-version",
|
|
9
|
+
".standard.yml",
|
|
10
|
+
"Gemfile",
|
|
11
|
+
"Gemfile.lock",
|
|
12
|
+
"Rakefile",
|
|
13
|
+
"Steepfile",
|
|
14
|
+
"bin/**",
|
|
15
|
+
"examples/**",
|
|
16
|
+
"ext/prosody/extconf.rb",
|
|
17
|
+
"lib/**",
|
|
18
|
+
"prosody.gemspec",
|
|
19
|
+
"sig-private/**",
|
|
20
|
+
"sig/**",
|
|
21
|
+
"spec/**",
|
|
22
|
+
"steep_expectations.yml",
|
|
23
|
+
"typecheck/**",
|
|
24
|
+
"typecheck_negative/**",
|
|
25
|
+
]
|
|
26
|
+
scope = "repository"
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.3.
|
|
1
|
+
3.3.12
|
data/.taplo.toml
CHANGED
data/AGENTS.md
CHANGED
|
@@ -10,11 +10,11 @@ Ruby/Rust bridge in depth — read it before touching the extension.
|
|
|
10
10
|
|
|
11
11
|
These come before everything else. Every change is judged against them.
|
|
12
12
|
|
|
13
|
-
**Write code that is simple, clear, well-factored,
|
|
14
|
-
understand, correct, and idiomatic.** A reader should grasp the intent
|
|
15
|
-
effort. If a change makes the code harder to read, the change is
|
|
16
|
-
if it is faster or shorter. If two designs are correct, pick the
|
|
17
|
-
easier to delete.
|
|
13
|
+
**Write code that is simple, clear, well-factored, DRY, performant, elegant,
|
|
14
|
+
easy to understand, correct, and idiomatic.** A reader should grasp the intent
|
|
15
|
+
without effort. If a change makes the code harder to read, the change is
|
|
16
|
+
wrong, even if it is faster or shorter. If two designs are correct, pick the
|
|
17
|
+
one that is easier to delete.
|
|
18
18
|
|
|
19
19
|
**Make invalid states unrepresentable in the type system.** When a compiler
|
|
20
20
|
or type checker can prove a contract, no test, comment, or convention has to.
|
|
@@ -26,9 +26,10 @@ uncompilable, do that instead of writing a runtime check.
|
|
|
26
26
|
**Delete more than you add.** Every change should leave the codebase smaller,
|
|
27
27
|
simpler, or both. If you must add code, look first for duplication you can
|
|
28
28
|
fold, abstractions that no longer pay rent, dead branches, and stale comments.
|
|
29
|
-
The end-state diff should net negative whenever the task allows.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
The end-state diff should net negative whenever the task allows. Each added
|
|
30
|
+
line must be inherent to the problem, not incidental to the solution. Line
|
|
31
|
+
count is not the only axis: plain duplicated arms often read better than
|
|
32
|
+
generic machinery.
|
|
32
33
|
|
|
33
34
|
**Identify, document, and enforce invariants.** For every load-bearing piece
|
|
34
35
|
of state: name the invariant, write it down near the type or function that
|
|
@@ -40,6 +41,15 @@ not yet understand the code well enough to change it.
|
|
|
40
41
|
encouraged when they are scoped to the area you are already touching. Do not
|
|
41
42
|
sprawl — but do not walk past obvious cleanup either.
|
|
42
43
|
|
|
44
|
+
**Do not break users without a reason.** The published package has real
|
|
45
|
+
users. A release can break them only for a clear improvement that the
|
|
46
|
+
owner decided on: a fixed defect, an invalid state that the types now
|
|
47
|
+
prevent, or a need of a new feature. "Nothing in this repo calls it" is
|
|
48
|
+
not a reason to remove, rename, or narrow a public member, an error
|
|
49
|
+
class, a log text, or a dependency floor. When you are not sure, keep
|
|
50
|
+
the old shape. List each break under `## Breaking changes` in the PR
|
|
51
|
+
body.
|
|
52
|
+
|
|
43
53
|
## Definition of Done
|
|
44
54
|
|
|
45
55
|
No change is complete until every line below holds. These are acts, not
|
|
@@ -60,7 +70,8 @@ aspirations — perform each one; do not merely agree with it:
|
|
|
60
70
|
vocabulary (see Redesign hygiene). "The new thing works" is half done.
|
|
61
71
|
7. Every claim written this session — doc cross-reference, "covered by" note,
|
|
62
72
|
exemplar path — was verified to resolve, not recalled from memory.
|
|
63
|
-
8. The diff is net-negative, or each addition is individually justified
|
|
73
|
+
8. The diff is net-negative, or each addition is individually justified as
|
|
74
|
+
inherent to the problem.
|
|
64
75
|
|
|
65
76
|
## Development Setup
|
|
66
77
|
|
|
@@ -216,6 +227,8 @@ half-deleted designs are where bloat and bug re-introduction live:
|
|
|
216
227
|
|
|
217
228
|
**Style:**
|
|
218
229
|
|
|
230
|
+
- Structure function bodies as logical paragraphs. Keep statements for one
|
|
231
|
+
operation together. Add a blank line before a new concept or operation.
|
|
219
232
|
- Prefer `use` statements over fully qualified prefixes
|
|
220
233
|
- Methods without `self` should be functions (except `new` and similar)
|
|
221
234
|
- Ask before large structural changes
|
|
@@ -295,12 +308,13 @@ Constants → Statics → Types → Implementations → Functions → Errors (bo
|
|
|
295
308
|
|
|
296
309
|
- `sig/` — public RBS signatures; `sig-private/` — internal signatures.
|
|
297
310
|
Every API change updates the signatures in the same commit.
|
|
298
|
-
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
311
|
+
- Public API docs are YARD comments on the public Ruby classes. A public
|
|
312
|
+
class that the Rust extension implements (`Client`, `Context`, `Message`,
|
|
313
|
+
`Timer`) keeps a documented stub in `lib/prosody/native_stubs/`. Do not
|
|
314
|
+
write stubs for internal native classes or private methods; `sig/` and
|
|
315
|
+
`sig-private/` type them. The runtime does not load the stubs, and Steep
|
|
316
|
+
ignores them. When a public native method changes, update its stub in the
|
|
317
|
+
same commit.
|
|
304
318
|
- Steep targets (`Steepfile`): `lib` checks the implementation;
|
|
305
319
|
`consumer_types` (`typecheck/`) verifies a payload type flows through the
|
|
306
320
|
public API; `typed_examples` checks every runnable example;
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0](https://github.com/prosody-events/prosody-rb/compare/prosody/v0.5.1...prosody/v0.6.0) (2026-10-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **deps:** upgrade prosody to 0.7.0 ([#51](https://github.com/prosody-events/prosody-rb/issues/51)) ([c9f265c](https://github.com/prosody-events/prosody-rb/commit/c9f265c7c5353c989886d2333afca2142e06763c))
|
|
9
|
+
|
|
3
10
|
## [0.5.1](https://github.com/prosody-events/prosody-rb/compare/prosody/v0.5.0...prosody/v0.5.1) (2026-08-20)
|
|
4
11
|
|
|
5
12
|
|
data/CLAUDE.md
CHANGED
|
@@ -10,11 +10,11 @@ Ruby/Rust bridge in depth — read it before touching the extension.
|
|
|
10
10
|
|
|
11
11
|
These come before everything else. Every change is judged against them.
|
|
12
12
|
|
|
13
|
-
**Write code that is simple, clear, well-factored,
|
|
14
|
-
understand, correct, and idiomatic.** A reader should grasp the intent
|
|
15
|
-
effort. If a change makes the code harder to read, the change is
|
|
16
|
-
if it is faster or shorter. If two designs are correct, pick the
|
|
17
|
-
easier to delete.
|
|
13
|
+
**Write code that is simple, clear, well-factored, DRY, performant, elegant,
|
|
14
|
+
easy to understand, correct, and idiomatic.** A reader should grasp the intent
|
|
15
|
+
without effort. If a change makes the code harder to read, the change is
|
|
16
|
+
wrong, even if it is faster or shorter. If two designs are correct, pick the
|
|
17
|
+
one that is easier to delete.
|
|
18
18
|
|
|
19
19
|
**Make invalid states unrepresentable in the type system.** When a compiler
|
|
20
20
|
or type checker can prove a contract, no test, comment, or convention has to.
|
|
@@ -26,9 +26,10 @@ uncompilable, do that instead of writing a runtime check.
|
|
|
26
26
|
**Delete more than you add.** Every change should leave the codebase smaller,
|
|
27
27
|
simpler, or both. If you must add code, look first for duplication you can
|
|
28
28
|
fold, abstractions that no longer pay rent, dead branches, and stale comments.
|
|
29
|
-
The end-state diff should net negative whenever the task allows.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
The end-state diff should net negative whenever the task allows. Each added
|
|
30
|
+
line must be inherent to the problem, not incidental to the solution. Line
|
|
31
|
+
count is not the only axis: plain duplicated arms often read better than
|
|
32
|
+
generic machinery.
|
|
32
33
|
|
|
33
34
|
**Identify, document, and enforce invariants.** For every load-bearing piece
|
|
34
35
|
of state: name the invariant, write it down near the type or function that
|
|
@@ -40,6 +41,15 @@ not yet understand the code well enough to change it.
|
|
|
40
41
|
encouraged when they are scoped to the area you are already touching. Do not
|
|
41
42
|
sprawl — but do not walk past obvious cleanup either.
|
|
42
43
|
|
|
44
|
+
**Do not break users without a reason.** The published package has real
|
|
45
|
+
users. A release can break them only for a clear improvement that the
|
|
46
|
+
owner decided on: a fixed defect, an invalid state that the types now
|
|
47
|
+
prevent, or a need of a new feature. "Nothing in this repo calls it" is
|
|
48
|
+
not a reason to remove, rename, or narrow a public member, an error
|
|
49
|
+
class, a log text, or a dependency floor. When you are not sure, keep
|
|
50
|
+
the old shape. List each break under `## Breaking changes` in the PR
|
|
51
|
+
body.
|
|
52
|
+
|
|
43
53
|
## Definition of Done
|
|
44
54
|
|
|
45
55
|
No change is complete until every line below holds. These are acts, not
|
|
@@ -60,7 +70,8 @@ aspirations — perform each one; do not merely agree with it:
|
|
|
60
70
|
vocabulary (see Redesign hygiene). "The new thing works" is half done.
|
|
61
71
|
7. Every claim written this session — doc cross-reference, "covered by" note,
|
|
62
72
|
exemplar path — was verified to resolve, not recalled from memory.
|
|
63
|
-
8. The diff is net-negative, or each addition is individually justified
|
|
73
|
+
8. The diff is net-negative, or each addition is individually justified as
|
|
74
|
+
inherent to the problem.
|
|
64
75
|
|
|
65
76
|
## Development Setup
|
|
66
77
|
|
|
@@ -216,6 +227,8 @@ half-deleted designs are where bloat and bug re-introduction live:
|
|
|
216
227
|
|
|
217
228
|
**Style:**
|
|
218
229
|
|
|
230
|
+
- Structure function bodies as logical paragraphs. Keep statements for one
|
|
231
|
+
operation together. Add a blank line before a new concept or operation.
|
|
219
232
|
- Prefer `use` statements over fully qualified prefixes
|
|
220
233
|
- Methods without `self` should be functions (except `new` and similar)
|
|
221
234
|
- Ask before large structural changes
|
|
@@ -295,12 +308,13 @@ Constants → Statics → Types → Implementations → Functions → Errors (bo
|
|
|
295
308
|
|
|
296
309
|
- `sig/` — public RBS signatures; `sig-private/` — internal signatures.
|
|
297
310
|
Every API change updates the signatures in the same commit.
|
|
298
|
-
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
311
|
+
- Public API docs are YARD comments on the public Ruby classes. A public
|
|
312
|
+
class that the Rust extension implements (`Client`, `Context`, `Message`,
|
|
313
|
+
`Timer`) keeps a documented stub in `lib/prosody/native_stubs/`. Do not
|
|
314
|
+
write stubs for internal native classes or private methods; `sig/` and
|
|
315
|
+
`sig-private/` type them. The runtime does not load the stubs, and Steep
|
|
316
|
+
ignores them. When a public native method changes, update its stub in the
|
|
317
|
+
same commit.
|
|
304
318
|
- Steep targets (`Steepfile`): `lib` checks the implementation;
|
|
305
319
|
`consumer_types` (`typecheck/`) verifies a payload type flows through the
|
|
306
320
|
public API; `typed_examples` checks every runnable example;
|
data/CONFIGURATION.md
CHANGED
|
@@ -10,12 +10,14 @@ The Ruby client reports values it cannot convert to Prosody types. Prosody valid
|
|
|
10
10
|
|-----------------------------------------|---------------------------------------------------|--------------|
|
|
11
11
|
| `bootstrap_servers` / `PROSODY_BOOTSTRAP_SERVERS` | Kafka servers to connect to | - |
|
|
12
12
|
| `group_id` / `PROSODY_GROUP_ID` | Consumer group name | - |
|
|
13
|
-
| `subscribed_topics` / `PROSODY_SUBSCRIBED_TOPICS` | Topics to read from
|
|
13
|
+
| `subscribed_topics` / `PROSODY_SUBSCRIBED_TOPICS` | Topics to read from; a client that only reads published state needs none | - |
|
|
14
14
|
| `allowed_events` / `PROSODY_ALLOWED_EVENTS` | Only process events matching these prefixes | (all) |
|
|
15
15
|
| `source_system` / `PROSODY_SOURCE_SYSTEM` | Tag for outgoing messages (prevents reprocessing)| `<group_id>` |
|
|
16
16
|
| `mock` / `PROSODY_MOCK` | Use in-memory Kafka for testing | false |
|
|
17
17
|
| `mode` / - | Processing mode: `pipeline`, `low_latency`, or `best_effort` | `pipeline` |
|
|
18
|
-
| - / `PROSODY_LOG` | Rust log filter, such as `info` or `prosody=debug` | `info` |
|
|
18
|
+
| - / `PROSODY_LOG` | Rust log filter, such as `info` or `prosody=debug` | `info`, with `warn` for `scylla` and `opentelemetry` |
|
|
19
|
+
|
|
20
|
+
`PROSODY_LOG` directives apply on top of the defaults above. A value that names only targets, such as `prosody=debug`, keeps other targets at `info`. Set `PROSODY_LOG=opentelemetry=info` to restore the OpenTelemetry info events.
|
|
19
21
|
|
|
20
22
|
## Requests
|
|
21
23
|
|
|
@@ -45,9 +47,9 @@ Set `subsystem` to make this client answer requests. Without it, the client cons
|
|
|
45
47
|
| `shutdown_timeout` / `PROSODY_SHUTDOWN_TIMEOUT` | Shutdown budget; handlers run freely until cancellation fires near the end of the timeout | 30s |
|
|
46
48
|
| `stall_threshold` / `PROSODY_STALL_THRESHOLD` | Report unhealthy if no progress for this long | 5m |
|
|
47
49
|
| `probe_port` / `PROSODY_PROBE_PORT` | HTTP port for health checks; use `false`, `:disabled`, or the environment value `none` to disable | 8000 |
|
|
48
|
-
|
|
|
50
|
+
| `statistics_interval` / `PROSODY_STATISTICS_INTERVAL` | How often librdkafka reports client statistics; must be between 1ms and 24h | 5s |
|
|
49
51
|
| `failure_topic` / `PROSODY_FAILURE_TOPIC` | Send unprocessable messages here (dead letter queue) | - |
|
|
50
|
-
| `idempotence_cache_size` / `PROSODY_IDEMPOTENCE_CACHE_SIZE` |
|
|
52
|
+
| `idempotence_cache_size` / `PROSODY_IDEMPOTENCE_CACHE_SIZE` | Capacity of the producer idempotence cache and of the consumer deduplication cache. Consumer deduplication cannot be turned off, so the value must be at least 1 | 8192 |
|
|
51
53
|
| `idempotence_version` / `PROSODY_IDEMPOTENCE_VERSION` | Version string for cache-busting dedup hashes | 1 |
|
|
52
54
|
| `idempotence_ttl` / `PROSODY_IDEMPOTENCE_TTL` | TTL for dedup records in Cassandra | 7d (604800 seconds) |
|
|
53
55
|
| `slab_size` / `PROSODY_SLAB_SIZE` | Timer storage granularity (rarely needs changing) | 1h |
|
|
@@ -141,27 +143,28 @@ Register keyed-state collections before you subscribe. Persistence is backed by
|
|
|
141
143
|
| Option / Environment Variable | Description | Default |
|
|
142
144
|
|-------------------------------|-------------|---------|
|
|
143
145
|
| `state_collections` / - | Keyed-state collections to register before subscribe (array of definitions or config hashes; duplicate names rejected) | (none) |
|
|
144
|
-
| `subsystem` / `PROSODY_SUBSYSTEM` | Subsystem name used to advertise JSON collections whose definitions set `published: true` | (none) |
|
|
145
|
-
| `state_cache_dir` / `PROSODY_STATE_CACHE_DIR` |
|
|
146
|
+
| `subsystem` / `PROSODY_SUBSYSTEM` | Subsystem name used to advertise JSON and set collections whose definitions set `published: true` | (none) |
|
|
147
|
+
| `state_cache_dir` / `PROSODY_STATE_CACHE_DIR` | Directory for the local keyed-state caches. Each consumer opens its cache in a new subdirectory and removes it when the consumer stops, so clients can share the directory. Set a mounted path in production | `<temp>/prosody/keyed-state` |
|
|
146
148
|
| `state_owned_cache_size` / `PROSODY_STATE_OWNED_CACHE_SIZE` | Capacity of the owning keyed-state cache; accepts sizes such as `64 MiB` or `500 MB` | storage-engine default |
|
|
149
|
+
| `state_memtable_size` / `PROSODY_STATE_MEMTABLE_SIZE` | Bytes of in-memory writes the local keyed-state cache holds for each assigned partition before it flushes them to disk; accepts sizes such as `16 MiB`. Memory use scales with the number of assigned partitions | storage-engine default of 64 MiB |
|
|
147
150
|
| `state_read_cache_size` / `PROSODY_STATE_READ_CACHE_SIZE` | Capacity of the published-state read cache; accepts sizes such as `1 MiB` | `state_owned_cache_size` or `PROSODY_STATE_OWNED_CACHE_SIZE` when set; otherwise 1 MiB |
|
|
148
151
|
| `state_read_cache` / `PROSODY_STATE_READ_CACHE_TTL` | Default published-read cache TTL. Use `false` or the environment value `none` to bypass the cache | 5s |
|
|
149
|
-
| `state_recovery_delay` / `PROSODY_STATE_RECOVERY_DELAY` | Whole-second delay between staging a provisional cell and the recovery sweep; every collection TTL must strictly exceed it | 30s |
|
|
150
|
-
|
|
151
|
-
Prefer the definition constructors from the [API reference](README.md#api-reference). They serialize into `state_collections`, so you can reuse the same object with `context.state`. Each entry has these fields:
|
|
152
152
|
|
|
153
153
|
Published collections require `subsystem`. Keep it configured for one deployment after removing `published: true` so readers can observe the collection's retirement.
|
|
154
154
|
|
|
155
|
+
Prefer the definition constructors from the [API reference](README.md#api-reference). They serialize into `state_collections`, so you can reuse the same object with `context.state`. Each entry has these fields:
|
|
156
|
+
|
|
155
157
|
| Field | Description | Default |
|
|
156
158
|
|-------|-------------|---------|
|
|
157
159
|
| `name` | Collection name; non-empty and unique within the client | (required) |
|
|
158
|
-
| `kind` | `"value"`, `"map"`, or `"deque"` | (required) |
|
|
159
|
-
| `payload` | `"json"` (JSON values) or `"message"` (the full Kafka message the handler received) | (required) |
|
|
160
|
-
| `ttl_seconds` | Per-write TTL in whole seconds (at least 1
|
|
160
|
+
| `kind` | `"value"`, `"map"`, `"set"`, or `"deque"` | (required) |
|
|
161
|
+
| `payload` | `"json"` (JSON values) or `"message"` (the full Kafka message the handler received). Omit it for a set, which stores membership only | (required for a value, map, or deque) |
|
|
162
|
+
| `ttl_seconds` | Per-write TTL in whole seconds (at least 1) | (none) |
|
|
161
163
|
| `read_uncommitted` | Opt out of transactional staging | false |
|
|
162
|
-
| `published` | Allow read-only access from other consumer groups; JSON collections only | false |
|
|
163
|
-
| `
|
|
164
|
-
| `keyset_limit` | Map-only; ordered-scan bound in `0..=4096` (`0` disables ordered-scan tracking) | 128 |
|
|
164
|
+
| `published` | Allow read-only access from other consumer groups; JSON and set collections only | false |
|
|
165
|
+
| `keyset_limit` | Map and set only; ordered-scan bound in `0..=4096` (`0` disables ordered-scan tracking) | 128 |
|
|
165
166
|
| `capacity` | Deque-only window bound (at least 1); keeps at most N slots, enforced lazily on push. Runtime-only and mutable across deploys — not persisted | unbounded |
|
|
166
167
|
|
|
167
|
-
Constructors set these via keyword arguments (`ttl:`, `keyset_limit:`, `capacity:`, `read_uncommitted:`, `published
|
|
168
|
+
Constructors set these via keyword arguments (`ttl:`, `keyset_limit:`, `capacity:`, `read_uncommitted:`, `published:`).
|
|
169
|
+
|
|
170
|
+
A JSON or set constructor also takes `read_cache:`. It is not a registration field: it applies only to the readers that `client.state` opens with the definition. `read_cache` is a positive duration in seconds, `false` to bypass the cache, or `nil` to inherit the client default.
|
data/README.md
CHANGED
|
@@ -277,10 +277,10 @@ You can monitor the stall state programmatically using the client's methods:
|
|
|
277
277
|
|
|
278
278
|
```ruby
|
|
279
279
|
# Get the number of partitions currently assigned to this consumer
|
|
280
|
-
partition_count = client.
|
|
280
|
+
partition_count = client.assigned_partition_count
|
|
281
281
|
|
|
282
282
|
# Check if the consumer has stalled partitions
|
|
283
|
-
if client.
|
|
283
|
+
if client.stalled?
|
|
284
284
|
warn 'Consumer has stalled partitions'
|
|
285
285
|
end
|
|
286
286
|
```
|
|
@@ -299,7 +299,7 @@ Send a request from a handler or other application code. The Prosody client does
|
|
|
299
299
|
|
|
300
300
|
Do not rely on hash order. The hash contains one entry for each selected subsystem. A missing response becomes a timeout `Failure`; Prosody does not omit the subsystem. The request raises an error for request-level failures, such as invalid input, a Kafka send failure, or shutdown. Do not wait for a request if the current consumer group must process it for the same key. That group cannot process it until the handler returns.
|
|
301
301
|
|
|
302
|
-
Message and excise handler return values become successful outcomes. Each return value must have a JSON representation.
|
|
302
|
+
Message and excise handler return values become successful outcomes. Each return value must have a JSON representation. A return value without one is a transient handler error, so Prosody retries the message.
|
|
303
303
|
|
|
304
304
|
Set `subsystem` to `inventory` on the client that subscribes this handler.
|
|
305
305
|
|
|
@@ -626,12 +626,53 @@ State operations look synchronous. They yield the current fiber while Prosody pe
|
|
|
626
626
|
| Collection | JSON payload | Kafka message | Main operations |
|
|
627
627
|
| --- | --- | --- | --- |
|
|
628
628
|
| Value | `Prosody.value` | `Prosody.message_value` | `get`, `set`, `clear` |
|
|
629
|
-
| Ordered string map | `Prosody.map` | `Prosody.message_map` | `get`, `get_many`, `key?`, `set`, `delete`, `each_pair`, `each_key`, `clear` |
|
|
629
|
+
| Ordered string map | `Prosody.map` | `Prosody.message_map` | `get`, `get_many`, `key?`, `contains_many`, `empty?`, `set`, `delete`, `each_pair`, `each_key`, `clear` |
|
|
630
|
+
| Ordered string set | `Prosody.set` | - | `add` / `<<`, `delete`, `include?`, `contains_many`, `empty?`, `each`, `clear` |
|
|
630
631
|
| Deque | `Prosody.deque` | `Prosody.message_deque` | `push`, `unshift`, `pop`, `shift`, `get`, `length`, `each`, `clear` |
|
|
631
632
|
|
|
632
|
-
Map and deque scans return enumerators when called without a block. Map keys are strings.
|
|
633
|
+
Map, set, and deque scans return enumerators when called without a block. Map keys and set members are strings. A set stores membership only, so it has no payload type.
|
|
633
634
|
|
|
634
|
-
`nil`
|
|
635
|
+
A value, map, or deque read returns `nil` when no value is present. Do not store `nil` as a value. A `nil` write fails with a `PermanentStateError`. Use `clear` (value, deque) or `delete` (map) to remove a value.
|
|
636
|
+
|
|
637
|
+
A set handle mirrors Ruby's `Set`:
|
|
638
|
+
|
|
639
|
+
```ruby
|
|
640
|
+
SEEN_ORDERS = Prosody.set("seen-orders", ttl: 7 * 24 * 60 * 60)
|
|
641
|
+
|
|
642
|
+
def on_message(context, message)
|
|
643
|
+
seen = context.state(SEEN_ORDERS)
|
|
644
|
+
return if seen.include?(message.payload["order_id"])
|
|
645
|
+
|
|
646
|
+
seen << message.payload["order_id"]
|
|
647
|
+
fulfill(message)
|
|
648
|
+
end
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Query keywords
|
|
652
|
+
|
|
653
|
+
Every traversal method accepts optional query keywords. Prosody applies them in storage, so a query reads only the selected entries. The `each` and `each_*` methods iterate forward. The `reverse_each` and `reverse_each_*` methods iterate backward.
|
|
654
|
+
|
|
655
|
+
| Keyword | Selects |
|
|
656
|
+
| --- | --- |
|
|
657
|
+
| `from:` / `after:` | Starts at the key or position, or just after it, in iteration order |
|
|
658
|
+
| `to:` / `before:` | Stops at the key or position, or just before it, in iteration order |
|
|
659
|
+
| `range:` | Keeps keys or positions in a Ruby `Range`: `"a".."m"`, `"a"..."m"`, `.."m"`, or `"a"..`. Write the range in ascending order; it applies in both directions. A descending range is empty |
|
|
660
|
+
| `prefix:` | Keeps map keys or set members that start with the string |
|
|
661
|
+
| `limit:` | Stops after this many items; a positive `Integer` |
|
|
662
|
+
|
|
663
|
+
A reverse traversal starts at the high end. Keywords narrow the selection and never widen it. Pass at most one of `from:` and `after:`, and at most one of `to:` and `before:`. Set traversals select members. Deque positions count from the front and must be non-negative. To read the last N items, use `reverse_each(limit: N)`. Deques have no `prefix:`. A bad keyword raises `ArgumentError` or `TypeError`.
|
|
664
|
+
|
|
665
|
+
To read a map in pages, pass the last key of the previous page as `after:`:
|
|
666
|
+
|
|
667
|
+
```ruby
|
|
668
|
+
page = map.each_pair(prefix: "order:", limit: 100).to_a
|
|
669
|
+
until page.empty?
|
|
670
|
+
page.each { |key, order| archive(key, order) }
|
|
671
|
+
page = map.each_pair(prefix: "order:", after: page.last.first, limit: 100).to_a
|
|
672
|
+
end
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
The same pattern works backward with `reverse_each_pair`.
|
|
635
676
|
|
|
636
677
|
### When keyed-state changes become visible
|
|
637
678
|
|
|
@@ -643,6 +684,8 @@ This transaction applies only to keyed state. Some workflows need state changes
|
|
|
643
684
|
- `commit` commits the collection's pending changes before the handler ends. A later handler failure does not remove them.
|
|
644
685
|
- `rollback` discards pending changes since the last `commit`. It cannot undo committed changes.
|
|
645
686
|
|
|
687
|
+
`commit` and `rollback` return `:applied` when they wrote or discarded pending changes. They return `:no_op` when the collection had no pending changes.
|
|
688
|
+
|
|
646
689
|
### Published state
|
|
647
690
|
|
|
648
691
|
Some callers need only the current value for a key. They can accept a stale value or a race with a concurrent update.
|
|
@@ -665,7 +708,7 @@ current_order = context.state(CURRENT_ORDER)
|
|
|
665
708
|
current_order.set({"sku" => "book"})
|
|
666
709
|
```
|
|
667
710
|
|
|
668
|
-
Read published state from a handler or other application code. The Prosody client does not need an active subscription.
|
|
711
|
+
Read published state from a handler or other application code. The Prosody client does not need an active subscription. A client that only reads published state needs no `subscribed_topics`.
|
|
669
712
|
|
|
670
713
|
Use the subsystem and the same definition to open a reader:
|
|
671
714
|
|
|
@@ -674,9 +717,9 @@ order_reader = client.state("checkout", CURRENT_ORDER)
|
|
|
674
717
|
current_order = order_reader.get("customer-123")
|
|
675
718
|
```
|
|
676
719
|
|
|
677
|
-
The reader cannot see pending changes that exist only in a handler. It cannot change the collection. Each read takes an explicit key because no handler supplies one.
|
|
720
|
+
The reader cannot see pending changes that exist only in a handler. It cannot change the collection. Each read takes an explicit String key because no handler supplies one.
|
|
678
721
|
|
|
679
|
-
Map and deque readers fetch data in chunks. They do not load the complete collection before iteration starts. Readers return an `Enumerator` without a block.
|
|
722
|
+
Map, set, and deque readers fetch data in chunks. They do not load the complete collection before iteration starts. Readers return an `Enumerator` without a block. Reader traversals accept the same [query keywords](#query-keywords) after the key. A failed read raises `Prosody::TransientStateError` or `Prosody::PermanentStateError`, as an owned handle does, for point reads and traversals alike. A reader that Prosody cannot open, such as one with a zero `read_cache`, raises the same classes.
|
|
680
723
|
|
|
681
724
|
Use `reverse_each_pair`, `reverse_each_key`, `reverse_each_value`, or `reverse_each` for reverse traversal.
|
|
682
725
|
|
|
@@ -783,9 +826,9 @@ all traces to Ruby.
|
|
|
783
826
|
To use OpenTelemetry tracing with Prosody, you need to install the following gems:
|
|
784
827
|
|
|
785
828
|
```ruby
|
|
786
|
-
gem 'opentelemetry-sdk', '~> 1.
|
|
787
|
-
gem 'opentelemetry-api', '~> 1.
|
|
788
|
-
gem 'opentelemetry-exporter-otlp', '~> 0.
|
|
829
|
+
gem 'opentelemetry-sdk', '~> 1.13'
|
|
830
|
+
gem 'opentelemetry-api', '~> 1.10'
|
|
831
|
+
gem 'opentelemetry-exporter-otlp', '~> 0.36'
|
|
789
832
|
```
|
|
790
833
|
|
|
791
834
|
### Initializing Tracing
|
|
@@ -907,6 +950,16 @@ Call `shutdown` when the application terminates. It stops all client services an
|
|
|
907
950
|
client.shutdown
|
|
908
951
|
```
|
|
909
952
|
|
|
953
|
+
To scope a client to a block, use `Prosody::Client.open`. It yields the client and calls `shutdown` when the block exits, also when the block raises. It returns the value of the block.
|
|
954
|
+
|
|
955
|
+
```ruby
|
|
956
|
+
Prosody::Client.open(bootstrap_servers: "localhost:9092") do |client|
|
|
957
|
+
client.send_message("my-topic", "key", {"hello" => "world"})
|
|
958
|
+
end
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Repeated `shutdown` calls wait for the same operation, so the block can also call `shutdown`.
|
|
962
|
+
|
|
910
963
|
Handle application shutdown with signal handlers:
|
|
911
964
|
|
|
912
965
|
```ruby
|
|
@@ -972,6 +1025,18 @@ Best practices:
|
|
|
972
1025
|
- Be cautious with permanent errors as they prevent retries and can result in data loss.
|
|
973
1026
|
- Consider system reliability and data consistency when classifying errors.
|
|
974
1027
|
|
|
1028
|
+
A handler can tell a retry from a normal delivery through `context.demand`:
|
|
1029
|
+
|
|
1030
|
+
```ruby
|
|
1031
|
+
def on_message(context, message)
|
|
1032
|
+
demand = context.demand
|
|
1033
|
+
logger.warn("retry #{demand.retries} for #{message.key}") if demand.failure?
|
|
1034
|
+
process(message)
|
|
1035
|
+
end
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
`demand.retries` is the number of retries: 0 for a normal delivery and 1 on the first retry. After Prosody defers an event, the count starts again at 1. The count is an estimate. Keep an exact attempt count in keyed state if you need one.
|
|
1039
|
+
|
|
975
1040
|
### Handling Task Cancellation
|
|
976
1041
|
|
|
977
1042
|
Prosody cancels tasks during partition rebalancing, timeout, or shutdown. During shutdown, handlers run freely for most of the `shutdown_timeout` before the cancellation signal fires—giving in-flight work time to complete. When cancelled, your handler receives `Async::Stop` at the next yield point (I/O operation, sleep, etc.).
|
|
@@ -1064,23 +1129,24 @@ Ensure you have thoroughly tested your changes before merging to `main`.
|
|
|
1064
1129
|
### Prosody::Client
|
|
1065
1130
|
|
|
1066
1131
|
- `new(config)` or `new(**options)`: Create a client from a `Configuration`, hash, or keyword options.
|
|
1132
|
+
- `open(config) { |client| ... }`: Create a client, yield it, and shut it down when the block exits. It returns the value of the block.
|
|
1067
1133
|
- `send_message(String topic, String key, Prosody::json_value payload)`: Send a JSON-serializable message.
|
|
1068
1134
|
- `excise(String topic, String key)`: Send an excise record for a key.
|
|
1069
1135
|
- `request(topic:, key:, payload:, subsystems:, timeout:)`: Return one outcome for each subsystem.
|
|
1070
1136
|
- `request_excise(topic:, key:, subsystems:, timeout:)`: Return one excise outcome for each subsystem.
|
|
1071
1137
|
- `consumer_state`: Get the client state (`:shut_down`, `:unconfigured`, `:configured`, or `:running`).
|
|
1072
1138
|
- `source_system`: Get the source system identifier configured for the client.
|
|
1073
|
-
- `state(subsystem, definition)`: Open a typed, read-only published value, map, or deque.
|
|
1139
|
+
- `state(subsystem, definition)`: Open a typed, read-only published value, map, set, or deque.
|
|
1074
1140
|
- `subscribe(handler)`: Start event processing with the specified handler.
|
|
1075
1141
|
- `unsubscribe`: Stop the consumer. You can subscribe again later.
|
|
1076
1142
|
- `shutdown`: Stop all client services. Concurrent and repeated calls wait for the same operation.
|
|
1077
|
-
- `
|
|
1078
|
-
- `
|
|
1143
|
+
- `assigned_partition_count`: Get the number of partitions currently assigned to this consumer.
|
|
1144
|
+
- `stalled?`: Check if the consumer has stalled partitions.
|
|
1079
1145
|
|
|
1080
1146
|
### Prosody::AdminClient
|
|
1081
1147
|
|
|
1082
1148
|
- `new(bootstrap_servers)`: Create an admin client for the specified Kafka servers.
|
|
1083
|
-
- `create_topic(name, partitions, replication_factor)`: Create a Kafka topic.
|
|
1149
|
+
- `create_topic(name, partitions, replication_factor, cleanup_policy: nil, retention: nil)`: Create a Kafka topic. `cleanup_policy` is a Kafka cleanup policy such as `"delete"`, `"compact"`, or `"delete,compact"`. `retention` is the message retention in seconds. A `nil` keyword uses the cluster default.
|
|
1084
1150
|
- `delete_topic(name)`: Delete a Kafka topic.
|
|
1085
1151
|
|
|
1086
1152
|
### Prosody::EventHandler
|
|
@@ -1144,10 +1210,12 @@ Messages have the following attributes:
|
|
|
1144
1210
|
- `timestamp` (Time): The timestamp when the message was created or sent.
|
|
1145
1211
|
- `key` (String): The message key.
|
|
1146
1212
|
- `payload` (`Payload`): The JSON-deserialized message payload.
|
|
1213
|
+
- `source_system` (String or nil): The source system of the producer that sent the message, or `nil` when the message has none.
|
|
1214
|
+
- `response_requested?` (Boolean): Whether the producer requested a response. When it is false, Prosody discards the handler result, so a handler can skip work that only builds the response.
|
|
1147
1215
|
|
|
1148
1216
|
### Prosody::ExciseMessage
|
|
1149
1217
|
|
|
1150
|
-
An `ExciseMessage` has `topic`, `partition`, `offset`, `timestamp`,
|
|
1218
|
+
An `ExciseMessage` has `topic`, `partition`, `offset`, `timestamp`, `key`, `source_system`, and `response_requested?` attributes. It has no `payload` attribute.
|
|
1151
1219
|
|
|
1152
1220
|
### Prosody::Context
|
|
1153
1221
|
|
|
@@ -1155,7 +1223,8 @@ Represents the current event context:
|
|
|
1155
1223
|
|
|
1156
1224
|
- `should_cancel?`: Check if cancellation has been requested (includes timeout and shutdown).
|
|
1157
1225
|
- `on_cancel`: Wait until cancellation occurs.
|
|
1158
|
-
- `
|
|
1226
|
+
- `demand`: A `Prosody::Demand` that tells whether this call is a normal delivery or a retry. `kind` is `:normal` or `:failure`, and `normal?` and `failure?` test it. `retries` is the number of retries: 0 for a normal delivery and 1 on the first retry. The count is an estimate. Keep an exact attempt count in keyed state if you need one.
|
|
1227
|
+
- `state(definition)`: Bind a registered collection for the current attempt. An unregistered or mismatched definition raises `PermanentStateError`. See [Keyed State](#keyed-state).
|
|
1159
1228
|
|
|
1160
1229
|
Timer scheduling methods:
|
|
1161
1230
|
|
|
@@ -1186,6 +1255,7 @@ Definition constructors (each returns a frozen definition object used both in `C
|
|
|
1186
1255
|
|
|
1187
1256
|
- `Prosody.value(name, ttl: nil, read_uncommitted: nil, published: nil, read_cache: nil)`
|
|
1188
1257
|
- `Prosody.map(name, ttl: nil, keyset_limit: nil, read_uncommitted: nil, published: nil, read_cache: nil)`
|
|
1258
|
+
- `Prosody.set(name, ttl: nil, keyset_limit: nil, read_uncommitted: nil, published: nil, read_cache: nil)`
|
|
1189
1259
|
- `Prosody.deque(name, ttl: nil, capacity: nil, read_uncommitted: nil, published: nil, read_cache: nil)`
|
|
1190
1260
|
- `Prosody.message_value(name, ttl: nil, read_uncommitted: nil)`
|
|
1191
1261
|
- `Prosody.message_map(name, ttl: nil, keyset_limit: nil, read_uncommitted: nil)`
|
|
@@ -1195,13 +1265,15 @@ Each constructor returns a `StateDefinition`. It exposes `name`, `kind`, `payloa
|
|
|
1195
1265
|
|
|
1196
1266
|
Published readers take the user key as their first argument. `Prosody::PublishedValue` provides `get`.
|
|
1197
1267
|
|
|
1198
|
-
`Prosody::PublishedMap` provides `get`, `get_many`, `key?`, `has_key?`, `include?`, and `
|
|
1268
|
+
`Prosody::PublishedMap` provides `get`, `get_many`, `key?`, `has_key?`, `include?`, `member?`, `contains_many`, and `empty?`.
|
|
1199
1269
|
|
|
1200
1270
|
It provides `each` or `each_pair`, `each_key`, and `each_value`. The reverse methods are `reverse_each_pair`, `reverse_each_key`, and `reverse_each_value`.
|
|
1201
1271
|
|
|
1272
|
+
`Prosody::PublishedSet` provides `include?` or `member?`, `contains_many`, `empty?`, `each`, and `reverse_each`.
|
|
1273
|
+
|
|
1202
1274
|
`Prosody::PublishedDeque` provides `get`, `length` or `size`, `empty?`, `first`, `last`, `each`, and `reverse_each`.
|
|
1203
1275
|
|
|
1204
|
-
Traversal methods return an `Enumerator` without a block.
|
|
1276
|
+
Traversal methods return an `Enumerator` without a block. Every traversal accepts the optional [query keywords](#query-keywords) `from:`, `after:`, `to:`, `before:`, `range:`, and `limit:`. Map and set traversals also accept `prefix:`.
|
|
1205
1277
|
|
|
1206
1278
|
`Prosody::ValueState`:
|
|
1207
1279
|
|
|
@@ -1210,10 +1282,16 @@ Traversal methods return an `Enumerator` without a block.
|
|
|
1210
1282
|
`Prosody::MapState` (keys are `String`):
|
|
1211
1283
|
|
|
1212
1284
|
- `get` / `[]`, `get_many`, `set` / `[]=`, `store`, `delete`, and `clear`
|
|
1213
|
-
- `key?`, `has_key?`, `include?`, `member?`, `dig`, `slice`, `values_at`, `fetch`, and `fetch_values`
|
|
1285
|
+
- `key?`, `has_key?`, `include?`, `member?`, `contains_many`, `empty?`, `dig`, `slice`, `values_at`, `fetch`, and `fetch_values`
|
|
1214
1286
|
- `each` / `each_pair`, `each_key`, and `each_value`, including each reverse form
|
|
1215
1287
|
- `commit` and `rollback`
|
|
1216
1288
|
|
|
1289
|
+
`Prosody::SetState` (members are `String`):
|
|
1290
|
+
|
|
1291
|
+
- `add` / `<<`, `delete`, and `clear`, which return the set
|
|
1292
|
+
- `include?` / `member?`, `contains_many`, and `empty?`
|
|
1293
|
+
- `each` / `reverse_each`, `commit`, and `rollback`
|
|
1294
|
+
|
|
1217
1295
|
`Prosody::DequeState`:
|
|
1218
1296
|
|
|
1219
1297
|
- `push`, `append`, `<<`, `unshift`, `prepend`, `pop`, and `shift`
|
|
@@ -1223,8 +1301,7 @@ Traversal methods return an `Enumerator` without a block.
|
|
|
1223
1301
|
Errors:
|
|
1224
1302
|
|
|
1225
1303
|
- `Prosody::TransientStateError < Prosody::TransientError`: Reports a keyed-state error that Prosody can retry.
|
|
1226
|
-
- `Prosody::PermanentStateError < Prosody::PermanentError`: Reports a keyed-state error that another attempt cannot resolve.
|
|
1227
|
-
- `Prosody::NullValueError < Prosody::TransientStateError`: raised when a `nil` is written; use `clear`/`delete` instead.
|
|
1304
|
+
- `Prosody::PermanentStateError < Prosody::PermanentError`: Reports a keyed-state error that another attempt cannot resolve, such as a `nil` write.
|
|
1228
1305
|
|
|
1229
1306
|
Handler error types:
|
|
1230
1307
|
|