solid-objects 0.13.3 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/README.md +212 -67
- package/dist/actor.d.ts +1 -0
- package/dist/actor.d.ts.map +1 -1
- package/dist/actor.js +9 -0
- package/dist/actor.js.map +1 -1
- package/dist/broadcast-worker.d.ts +1 -1
- package/dist/broadcast-worker.d.ts.map +1 -1
- package/dist/broadcast-worker.js +1 -1
- package/dist/broadcast-worker.js.map +1 -1
- package/dist/broken-pipe.d.ts +5 -0
- package/dist/broken-pipe.d.ts.map +1 -0
- package/dist/broken-pipe.js +8 -0
- package/dist/broken-pipe.js.map +1 -0
- package/dist/browser/host.d.ts +8 -0
- package/dist/browser/host.d.ts.map +1 -0
- package/dist/browser/host.js +22 -0
- package/dist/browser/host.js.map +1 -0
- package/dist/browser/tab-host.d.ts +43 -0
- package/dist/browser/tab-host.d.ts.map +1 -0
- package/dist/browser/tab-host.js +196 -0
- package/dist/browser/tab-host.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/context.js +2 -2
- package/dist/context.js.map +1 -1
- package/dist/database/deadline.js +2 -2
- package/dist/database/deadline.js.map +1 -1
- package/dist/database/mysql.d.ts +1 -0
- package/dist/database/mysql.d.ts.map +1 -1
- package/dist/database/mysql.js +1 -0
- package/dist/database/mysql.js.map +1 -1
- package/dist/database/postgresql.d.ts +1 -0
- package/dist/database/postgresql.d.ts.map +1 -1
- package/dist/database/postgresql.js +1 -0
- package/dist/database/postgresql.js.map +1 -1
- package/dist/database/shared-sqlite-wasm.d.ts +74 -0
- package/dist/database/shared-sqlite-wasm.d.ts.map +1 -0
- package/dist/database/shared-sqlite-wasm.js +542 -0
- package/dist/database/shared-sqlite-wasm.js.map +1 -0
- package/dist/database/sqlite-wasm.d.ts +30 -0
- package/dist/database/sqlite-wasm.d.ts.map +1 -0
- package/dist/database/sqlite-wasm.js +185 -0
- package/dist/database/sqlite-wasm.js.map +1 -0
- package/dist/database/sqlite.d.ts +1 -0
- package/dist/database/sqlite.d.ts.map +1 -1
- package/dist/database/sqlite.js +1 -0
- package/dist/database/sqlite.js.map +1 -1
- package/dist/database/transaction-context.js +2 -2
- package/dist/database/transaction-context.js.map +1 -1
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/effect-worker.d.ts +1 -1
- package/dist/effect-worker.d.ts.map +1 -1
- package/dist/effect-worker.js +1 -1
- package/dist/effect-worker.js.map +1 -1
- package/dist/examples/quickstart-report.js +127 -0
- package/dist/examples/quickstart-report.js.map +1 -0
- package/dist/examples/sqlite-quickstart.js +59 -7
- package/dist/examples/sqlite-quickstart.js.map +1 -1
- package/dist/executable.js +4 -0
- package/dist/executable.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/platform/context-store.d.ts +11 -0
- package/dist/platform/context-store.d.ts.map +1 -0
- package/dist/platform/context-store.js +28 -0
- package/dist/platform/context-store.js.map +1 -0
- package/dist/platform/host-identity.d.ts +11 -0
- package/dist/platform/host-identity.d.ts.map +1 -0
- package/dist/platform/host-identity.js +17 -0
- package/dist/platform/host-identity.js.map +1 -0
- package/dist/platform/node.d.ts +2 -0
- package/dist/platform/node.d.ts.map +1 -0
- package/dist/platform/node.js +11 -0
- package/dist/platform/node.js.map +1 -0
- package/dist/platform/turn-context-store.d.ts +7 -0
- package/dist/platform/turn-context-store.d.ts.map +1 -0
- package/dist/platform/turn-context-store.js +17 -0
- package/dist/platform/turn-context-store.js.map +1 -0
- package/dist/platform/uuid.d.ts +2 -0
- package/dist/platform/uuid.d.ts.map +1 -0
- package/dist/platform/uuid.js +4 -0
- package/dist/platform/uuid.js.map +1 -0
- package/dist/platform/web-locks.d.ts +2 -0
- package/dist/platform/web-locks.d.ts.map +1 -0
- package/dist/platform/web-locks.js +8 -0
- package/dist/platform/web-locks.js.map +1 -0
- package/dist/reference.d.ts +16 -1
- package/dist/reference.d.ts.map +1 -1
- package/dist/reference.js +12 -0
- package/dist/reference.js.map +1 -1
- package/dist/reminder-scheduler.d.ts +1 -1
- package/dist/reminder-scheduler.d.ts.map +1 -1
- package/dist/reminder-scheduler.js +1 -1
- package/dist/reminder-scheduler.js.map +1 -1
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +13 -6
- package/dist/repository.js.map +1 -1
- package/dist/runtime.d.ts +25 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +62 -1
- package/dist/runtime.js.map +1 -1
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +2 -1
- package/dist/serialization.js.map +1 -1
- package/dist/signals.d.ts +9 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/signals.js +299 -0
- package/dist/signals.js.map +1 -0
- package/dist/transmit-effect.d.ts +2 -0
- package/dist/transmit-effect.d.ts.map +1 -0
- package/dist/transmit-effect.js +2 -0
- package/dist/transmit-effect.js.map +1 -0
- package/dist/transmit.d.ts +27 -0
- package/dist/transmit.d.ts.map +1 -0
- package/dist/transmit.js +105 -0
- package/dist/transmit.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +1 -0
- package/dist/web/index.js.map +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/docs/api.md +262 -20
- package/docs/architecture.md +50 -23
- package/docs/authorization.md +6 -5
- package/docs/benchmarks.md +87 -65
- package/docs/browser-protocol.md +62 -3
- package/docs/comparisons.md +14 -9
- package/docs/configuration.md +2 -2
- package/docs/correctness.md +30 -17
- package/docs/dashboard.md +6 -6
- package/docs/errors-and-recovery.md +12 -12
- package/docs/fit.md +5 -0
- package/docs/operations.md +33 -19
- package/docs/parity.md +137 -47
- package/docs/releasing.md +6 -6
- package/docs/state-and-lifecycle.md +12 -8
- package/docs/support.md +60 -20
- package/examples/quickstart-report.ts +151 -0
- package/examples/sqlite-quickstart.ts +76 -16
- package/package.json +37 -2
package/docs/benchmarks.md
CHANGED
|
@@ -83,72 +83,93 @@ dataset. Redirect stdout to retain the JSON result.
|
|
|
83
83
|
|
|
84
84
|
## Observed results
|
|
85
85
|
|
|
86
|
-
Measured on August
|
|
87
|
-
|
|
88
|
-
- Apple M5, 10 logical CPUs
|
|
89
|
-
- macOS 26.6
|
|
90
|
-
- Node.js
|
|
91
|
-
- SQLite 3.53.
|
|
92
|
-
|
|
86
|
+
Measured on August 22, 2026 with the `0.14.0` source tree:
|
|
87
|
+
|
|
88
|
+
- Apple M5 (Mac17,2), 10 logical CPUs
|
|
89
|
+
- macOS 26.6
|
|
90
|
+
- Node.js 24.18.0
|
|
91
|
+
- SQLite 3.53.1 through `node:sqlite` on the internal SSD
|
|
92
|
+
- PostgreSQL 17.11 and MySQL 9.7.1, both installed natively and started on a
|
|
93
|
+
scoped temporary data directory
|
|
93
94
|
- 25 warmup operations, 250 measured operations, concurrency 16
|
|
94
95
|
|
|
95
|
-
### SQLite 3.53.
|
|
96
|
-
|
|
97
|
-
| Topology | Shape | Handler | ops/s | p50 ms |
|
|
98
|
-
| -------------- | --------- | ------------ | -----: | -----: |
|
|
99
|
-
| one process | warm hot | synchronous |
|
|
100
|
-
| one process | warm hot | asynchronous |
|
|
101
|
-
| one process | warm many | synchronous |
|
|
102
|
-
| one process | warm many | asynchronous |
|
|
103
|
-
| one process | cold many | synchronous |
|
|
104
|
-
| one process | cold many | asynchronous |
|
|
105
|
-
| four processes | warm hot | synchronous |
|
|
106
|
-
| four processes | warm hot | asynchronous |
|
|
107
|
-
| four processes | warm many | synchronous |
|
|
108
|
-
| four processes | warm many | asynchronous |
|
|
109
|
-
| four processes | cold many | synchronous |
|
|
110
|
-
| four processes | cold many | asynchronous |
|
|
111
|
-
|
|
112
|
-
### PostgreSQL
|
|
113
|
-
|
|
114
|
-
| Topology | Shape | Handler |
|
|
115
|
-
| -------------- | --------- | ------------ |
|
|
116
|
-
| one process | warm hot | synchronous |
|
|
117
|
-
| one process | warm hot | asynchronous |
|
|
118
|
-
| one process | warm many | synchronous |
|
|
119
|
-
| one process | warm many | asynchronous |
|
|
120
|
-
| one process | cold many | synchronous |
|
|
121
|
-
| one process | cold many | asynchronous |
|
|
122
|
-
| four processes | warm hot | synchronous |
|
|
123
|
-
| four processes | warm hot | asynchronous |
|
|
124
|
-
| four processes | warm many | synchronous |
|
|
125
|
-
| four processes | warm many | asynchronous |
|
|
126
|
-
| four processes | cold many | synchronous |
|
|
127
|
-
| four processes | cold many | asynchronous |
|
|
128
|
-
|
|
129
|
-
### MySQL
|
|
130
|
-
|
|
131
|
-
| Topology | Shape | Handler |
|
|
132
|
-
| -------------- | --------- | ------------ |
|
|
133
|
-
| one process | warm hot | synchronous |
|
|
134
|
-
| one process | warm hot | asynchronous |
|
|
135
|
-
| one process | warm many | synchronous |
|
|
136
|
-
| one process | warm many | asynchronous |
|
|
137
|
-
| one process | cold many | synchronous |
|
|
138
|
-
| one process | cold many | asynchronous |
|
|
139
|
-
| four processes | warm hot | synchronous |
|
|
140
|
-
| four processes | warm hot | asynchronous |
|
|
141
|
-
| four processes | warm many | synchronous |
|
|
142
|
-
| four processes | warm many | asynchronous |
|
|
143
|
-
| four processes | cold many | synchronous |
|
|
144
|
-
| four processes | cold many | asynchronous |
|
|
145
|
-
|
|
146
|
-
The
|
|
147
|
-
observed limitations
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
96
|
+
### SQLite 3.53.1
|
|
97
|
+
|
|
98
|
+
| Topology | Shape | Handler | ops/s | p50 ms | p95 ms | p99 ms |
|
|
99
|
+
| -------------- | --------- | ------------ | -----: | -----: | -----: | -----: |
|
|
100
|
+
| one process | warm hot | synchronous | 286.09 | 50.09 | 135.15 | 176.55 |
|
|
101
|
+
| one process | warm hot | asynchronous | 322.53 | 48.66 | 70.59 | 75.87 |
|
|
102
|
+
| one process | warm many | synchronous | 119.05 | 132.25 | 237.52 | 247.05 |
|
|
103
|
+
| one process | warm many | asynchronous | 288.26 | 32.64 | 70.33 | 562.42 |
|
|
104
|
+
| one process | cold many | synchronous | 47.67 | 326.04 | 435.46 | 507.03 |
|
|
105
|
+
| one process | cold many | asynchronous | 99.39 | 148 | 236.03 | 295.26 |
|
|
106
|
+
| four processes | warm hot | synchronous | 518.66 | 30.15 | 52.73 | 78.57 |
|
|
107
|
+
| four processes | warm hot | asynchronous | 506.8 | 26.39 | 92.26 | 102.18 |
|
|
108
|
+
| four processes | warm many | synchronous | 190.36 | 76.65 | 145.7 | 158.94 |
|
|
109
|
+
| four processes | warm many | asynchronous | 77.12 | 194.6 | 399.43 | 527.1 |
|
|
110
|
+
| four processes | cold many | synchronous | 54.74 | 273.33 | 646.68 | 650.09 |
|
|
111
|
+
| four processes | cold many | asynchronous | 93.85 | 145.27 | 333.78 | 426.6 |
|
|
112
|
+
|
|
113
|
+
### PostgreSQL 17.11
|
|
114
|
+
|
|
115
|
+
| Topology | Shape | Handler | ops/s | p50 ms | p95 ms | p99 ms |
|
|
116
|
+
| -------------- | --------- | ------------ | -----: | -----: | -----: | ------: |
|
|
117
|
+
| one process | warm hot | synchronous | 206.31 | 69.33 | 132.69 | 153.03 |
|
|
118
|
+
| one process | warm hot | asynchronous | 212.07 | 72.38 | 96.75 | 106.12 |
|
|
119
|
+
| one process | warm many | synchronous | 191.4 | 55.71 | 147.99 | 722.6 |
|
|
120
|
+
| one process | warm many | asynchronous | 66.6 | 121.61 | 920.97 | 2441.28 |
|
|
121
|
+
| one process | cold many | synchronous | 31.38 | 500.73 | 705.15 | 722.54 |
|
|
122
|
+
| one process | cold many | asynchronous | 17.24 | 909.02 | 1192.9 | 1211.35 |
|
|
123
|
+
| four processes | warm hot | synchronous | 265.63 | 57.94 | 82.52 | 109.46 |
|
|
124
|
+
| four processes | warm hot | asynchronous | 330.94 | 46.81 | 63.76 | 70.25 |
|
|
125
|
+
| four processes | warm many | synchronous | 161.73 | 58.67 | 237.04 | 718.55 |
|
|
126
|
+
| four processes | warm many | asynchronous | 119.63 | 68.71 | 531.12 | 1372.5 |
|
|
127
|
+
| four processes | cold many | synchronous | 50.84 | 314.51 | 487.09 | 506.45 |
|
|
128
|
+
| four processes | cold many | asynchronous | 29.93 | 546.7 | 615.36 | 638.1 |
|
|
129
|
+
|
|
130
|
+
### MySQL 9.7.1
|
|
131
|
+
|
|
132
|
+
| Topology | Shape | Handler | ops/s | p50 ms | p95 ms | p99 ms |
|
|
133
|
+
| -------------- | --------- | ------------ | -----: | ------: | ------: | ------: |
|
|
134
|
+
| one process | warm hot | synchronous | 79.59 | 208.66 | 222.86 | 225.19 |
|
|
135
|
+
| one process | warm hot | asynchronous | 79.98 | 206.78 | 232.74 | 237.94 |
|
|
136
|
+
| one process | warm many | synchronous | 79.7 | 129.25 | 395.52 | 1987.46 |
|
|
137
|
+
| one process | warm many | asynchronous | 28.06 | 338.85 | 1992.54 | 6004.98 |
|
|
138
|
+
| one process | cold many | synchronous | 13.78 | 1215.91 | 1445.61 | 1470.91 |
|
|
139
|
+
| one process | cold many | asynchronous | 11.63 | 1347.74 | 1733.01 | 1820.26 |
|
|
140
|
+
| four processes | warm hot | synchronous | 213.75 | 72.85 | 94.48 | 106.73 |
|
|
141
|
+
| four processes | warm hot | asynchronous | 228.32 | 70.45 | 84.96 | 90.39 |
|
|
142
|
+
| four processes | warm many | synchronous | 105.31 | 90.36 | 297.88 | 1064.14 |
|
|
143
|
+
| four processes | warm many | asynchronous | 80.03 | 149.92 | 324.98 | 1090.95 |
|
|
144
|
+
| four processes | cold many | synchronous | 51.82 | 306.1 | 407.22 | 424.45 |
|
|
145
|
+
| four processes | cold many | asynchronous | 27.35 | 554.03 | 807.18 | 868.48 |
|
|
146
|
+
|
|
147
|
+
The cold and asynchronous cases keep the poorest throughput and the longest
|
|
148
|
+
tail. These are observed limitations. They are not capacity recommendations.
|
|
149
|
+
Repeat the runs on application-shaped payloads before you draw a general
|
|
150
|
+
conclusion. Integration tests cover PostgreSQL 14, MySQL 8.0, and other database
|
|
151
|
+
versions, but this harness did not measure them.
|
|
152
|
+
|
|
153
|
+
### Virtualization cost
|
|
154
|
+
|
|
155
|
+
The same server version ran natively and in Docker Desktop on the same machine,
|
|
156
|
+
on the same day, through the same harness. Only the container boundary changes.
|
|
157
|
+
|
|
158
|
+
| Database | Topology and shape | Native ops/s | Docker ops/s | Native gain |
|
|
159
|
+
| ---------------- | ------------------------------- | -----------: | -----------: | ----------: |
|
|
160
|
+
| PostgreSQL 17.11 | four processes, warm hot, async | 330.94 | 67.69 | 4.9x |
|
|
161
|
+
| PostgreSQL 17.11 | one process, warm hot, sync | 206.31 | 56.44 | 3.7x |
|
|
162
|
+
| MySQL 9.7.1 | four processes, warm hot, async | 228.32 | 60.27 | 3.8x |
|
|
163
|
+
| MySQL 9.7.1 | one process, warm hot, sync | 79.59 | 44.56 | 1.8x |
|
|
164
|
+
|
|
165
|
+
Across the full matrix, Docker Desktop cost between 1.0x and 7.8x of the native
|
|
166
|
+
throughput. The multi-process rows lose the most, because more connections and
|
|
167
|
+
more commits cross the container boundary. Measure a database on the deployment
|
|
168
|
+
shape you intend to run, and state the boundary with any number you publish.
|
|
169
|
+
|
|
170
|
+
Earlier releases of this document reported PostgreSQL 18.4 and MySQL 8.4.11 in
|
|
171
|
+
Docker Desktop on the `0.13.0` tree. Those numbers measured the container as
|
|
172
|
+
much as the database, so the tables above replace them.
|
|
152
173
|
|
|
153
174
|
## Sources of bias
|
|
154
175
|
|
|
@@ -156,7 +177,8 @@ covered by integration tests but were not benchmarked.
|
|
|
156
177
|
- Loopback database connections exclude production network latency.
|
|
157
178
|
- Filesystem cache, SQLite WAL state, Node JIT warmup, and garbage collection
|
|
158
179
|
affect short runs.
|
|
159
|
-
- Docker Desktop
|
|
180
|
+
- Docker Desktop costs between 1.0x and 7.8x of the native throughput. The
|
|
181
|
+
tables above use native servers. See [Virtualization cost](#virtualization-cost).
|
|
160
182
|
- The payload is a small counter, not a representative application state size.
|
|
161
183
|
- The harness measures default durability settings and one client concurrency.
|
|
162
184
|
- Hot-identity results deliberately include serialization and cannot be scaled
|
package/docs/browser-protocol.md
CHANGED
|
@@ -79,9 +79,9 @@ stale revisions within an incarnation.
|
|
|
79
79
|
|
|
80
80
|
`SolidObjectsComponentRegistry` maps changed observable names to keyed UI
|
|
81
81
|
registrations. The browser supplies an asynchronous `refresh` function and a
|
|
82
|
-
synchronous `apply` function
|
|
83
|
-
results use the same coordination contract
|
|
84
|
-
framework.
|
|
82
|
+
synchronous `apply` function. HTML, virtual DOM, and framework-native render
|
|
83
|
+
results therefore use the same coordination contract. The registry assumes no
|
|
84
|
+
render framework.
|
|
85
85
|
|
|
86
86
|
Components may share a batch name. A microtask unions affected components in
|
|
87
87
|
the same actor, batch, incarnation, and revision into one refresh request.
|
|
@@ -105,3 +105,62 @@ several WebSocket processes, the configured `broadcast` callback publishes the
|
|
|
105
105
|
committed envelope through a shared transport and each process passes received
|
|
106
106
|
envelopes to `runtime.realtime.publish()`. The session fence safely drops the
|
|
107
107
|
duplicate seen by a process that both claimed and received the same event.
|
|
108
|
+
|
|
109
|
+
## Tab host channel protocol
|
|
110
|
+
|
|
111
|
+
`solid-objects/browser/tab-host` uses a second, unrelated wire surface: a
|
|
112
|
+
`BroadcastChannel` between tabs of one origin. Every envelope carries
|
|
113
|
+
`protocol: "solid-objects-tab-host"` and `version: 1`; a listener ignores
|
|
114
|
+
anything else. Three kinds exist:
|
|
115
|
+
|
|
116
|
+
- `invoke`: a client request with a `requestId` (a UUID the client generates),
|
|
117
|
+
the target `actorType`, `actorId`, `operation`, and a JSON `arguments`
|
|
118
|
+
object.
|
|
119
|
+
- `result`: the leader's answer for one `requestId`, with either an `ok`
|
|
120
|
+
value or a named error.
|
|
121
|
+
- `leader-online`: the announcement a new leader posts on promotion. Clients
|
|
122
|
+
re-post their pending requests when they see it.
|
|
123
|
+
|
|
124
|
+
The client retries an `invoke` on an interval until a `result` arrives or its
|
|
125
|
+
timeout passes. The leader enqueues each request with `tab:<requestId>` as the
|
|
126
|
+
idempotency key, so a retried or re-posted request applies once. The channel
|
|
127
|
+
is same-origin plumbing between the application's own tabs; it carries no
|
|
128
|
+
authentication, so the trust boundary is the origin.
|
|
129
|
+
|
|
130
|
+
## Sync envelope
|
|
131
|
+
|
|
132
|
+
`solid-objects/transmit` transmits one JSON envelope per staged transmit
|
|
133
|
+
effect: `effectId`, target `actorType` and `actorId`, `operation`, and an
|
|
134
|
+
optional `arguments` object that defaults to an empty object on both
|
|
135
|
+
ingests. The transport belongs to the host application; the
|
|
136
|
+
Playwright suite posts envelopes over `fetch`. The server calls
|
|
137
|
+
`receiveTransmitEnvelope`, which enqueues an internal message with
|
|
138
|
+
`transmit:<effectId>` as the idempotency key, so a replayed envelope applies once.
|
|
139
|
+
Internal delivery skips `authorizeMessage`; the host must authenticate the
|
|
140
|
+
sender before that call.
|
|
141
|
+
|
|
142
|
+
## Shared database channel protocol
|
|
143
|
+
|
|
144
|
+
`solid-objects/database/shared-sqlite-wasm` uses a third wire surface: a
|
|
145
|
+
`BroadcastChannel` that carries SQL sessions from every tab to the current
|
|
146
|
+
database holder. Every envelope carries
|
|
147
|
+
`protocol: "solid-objects-shared-sqlite"` and `version: 1`. Seven kinds
|
|
148
|
+
exist:
|
|
149
|
+
|
|
150
|
+
- `ping` and `pong`: holder discovery. A new instance pings until a holder
|
|
151
|
+
answers with its `epoch`.
|
|
152
|
+
- `holder-online`: the announcement a new holder posts on promotion, with a
|
|
153
|
+
fresh `epoch`.
|
|
154
|
+
- `open`: start a session (`connection` or `transaction`) with a client
|
|
155
|
+
`sessionId`.
|
|
156
|
+
- `statement`: one `run`, `get`, `all`, or `now` operation inside a session.
|
|
157
|
+
- `close`: finish a session with `commit`, `rollback`, or `end`.
|
|
158
|
+
- `result`: the holder's answer for one `requestId`.
|
|
159
|
+
|
|
160
|
+
Every request carries the `epoch` it targets. A holder rejects requests from
|
|
161
|
+
another epoch, so a client learns about a failover from a fast rejection
|
|
162
|
+
rather than a timeout. A session that has not executed a statement retries
|
|
163
|
+
against the new holder automatically; later failures surface as
|
|
164
|
+
`SharedDatabaseFailover`, because a partially executed session must not
|
|
165
|
+
replay. The channel is same-origin plumbing with the origin as its trust
|
|
166
|
+
boundary, the same as the tab host protocol.
|
package/docs/comparisons.md
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
This guide compares coordination models so an application can choose the
|
|
4
4
|
smallest mechanism that meets its requirements. It does not rank the projects.
|
|
5
5
|
|
|
6
|
-
| Approach | State and serialization unit | Deployment and durable substrate
|
|
7
|
-
| --------------------------- | ---------------------------------------- |
|
|
8
|
-
| SQL transaction or row lock | Rows selected by one transaction | Application process and SQL database
|
|
9
|
-
| Traditional job queue | A job, queue, or configured grouping key | Workers plus broker or queue database
|
|
10
|
-
| Solid Objects | Actor class and application-defined ID | Node processes plus existing SQLite, PostgreSQL, or MySQL
|
|
11
|
-
| Cloudflare Durable Objects | Object class and globally unique ID | Cloudflare Workers plus per-object managed storage
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
6
|
+
| Approach | State and serialization unit | Deployment and durable substrate | Separate service | Replay versus state | Realtime and edge placement | Cross-identity transaction | Data access |
|
|
7
|
+
| --------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
8
|
+
| SQL transaction or row lock | Rows selected by one transaction | Application process and SQL database | No | The application retries a failed transaction | Application-owned | Yes, for rows in the same database transaction | Ordinary application tables and SQL tools |
|
|
9
|
+
| Traditional job queue | A job, queue, or configured grouping key | Workers plus broker or queue database | Usually | The job is retried; mutable entity state remains application-owned | Application-owned | Not supplied by the queue | Queue administration plus application data stores |
|
|
10
|
+
| Solid Objects | Actor class and application-defined ID | Node processes plus existing SQLite, PostgreSQL, or MySQL | No; Redis wake-up is optional | The operation retries against durable actor state | Committed projections; application-owned transport; no edge placement | No | Relational tables, typed administration, CLI, and dashboard |
|
|
11
|
+
| Cloudflare Durable Objects | Object class and globally unique ID | Cloudflare Workers plus per-object managed storage | Cloudflare platform | Object activation with durable state, not workflow-step replay | WebSockets and Cloudflare-selected object location | Storage transactions are scoped to one object | Object storage APIs and platform tooling |
|
|
12
|
+
| celld | Object class and object name | celld nodes plus one object-storage bucket; each object is its own SQLite database | Yes, the celld daemon on every node | The new owner restores the object's SQLite database from the bucket and resumes | Cloudflare Workers APIs; an object runs on one node of your fleet, not at an edge location | No | Per-object SQLite through the Workers storage APIs, plus the bucket |
|
|
13
|
+
| Rivet Actors | Addressable actor | Rivet Engine or managed compute with actor state, KV, or per-actor SQLite | Rivet Engine | Actor persistence and lifecycle; workflows add recorded steps | Actor events and deployment-dependent placement | No general transaction across actors | Actor APIs and selected persistence model |
|
|
14
|
+
| DBOS | Workflow ID and checkpointed steps | Application processes plus PostgreSQL system database | No orchestration server for the library; Conductor is recommended for distributed recovery | Deterministic workflow replay skips checkpointed steps | Workflow events; application placement | PostgreSQL transactions remain separate from workflow identity | PostgreSQL system database, client, CLI, and optional Conductor |
|
|
15
|
+
| Restate | Service handler or keyed virtual object | Application services plus Restate's durable log and state store | Yes | Durable execution journals handler progress and object state | Service protocol and Restate deployment | No shared SQL transaction across object keys | Restate APIs, state tools, snapshots, and backups |
|
|
15
16
|
|
|
16
17
|
## Primary references
|
|
17
18
|
|
|
@@ -22,6 +23,10 @@ smallest mechanism that meets its requirements. It does not rank the projects.
|
|
|
22
23
|
distinguishes local concurrency from multiple worker processes.
|
|
23
24
|
- Cloudflare documents global uniqueness, per-object storage, single-threaded
|
|
24
25
|
execution, and placement in [What are Durable Objects?](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/).
|
|
26
|
+
- celld documents per-object SQLite databases, bucket replication, and
|
|
27
|
+
object-storage compare-and-swap ownership in its
|
|
28
|
+
[repository](https://github.com/denoland/celld) and its
|
|
29
|
+
[documentation](https://celld.dev/docs).
|
|
25
30
|
- Rivet documents addressable actors and persistence in
|
|
26
31
|
[Actors](https://rivet.dev/docs/actors/) and
|
|
27
32
|
[Persistence](https://rivet.dev/docs/actors/persistence).
|
package/docs/configuration.md
CHANGED
|
@@ -59,8 +59,8 @@ runtime role enabled. Broadcast workers are started only when `broadcast` or
|
|
|
59
59
|
Consecutive empty passes double it up to
|
|
60
60
|
`idlePollingIntervalMilliseconds`. Actor workers never wait longer than
|
|
61
61
|
`leaseRenewalIntervalMilliseconds`. Set the fast and idle values equal for a
|
|
62
|
-
fixed cadence. A custom wake-up adapter
|
|
63
|
-
and `false` for a timeout
|
|
62
|
+
fixed cadence. A custom wake-up adapter must return `true` for a notification
|
|
63
|
+
and `false` for a timeout. An older adapter that returns `void` remains
|
|
64
64
|
compatible and keeps the fast cadence. Wake-ups reduce latency, while database
|
|
65
65
|
polling remains the correctness path.
|
|
66
66
|
|
package/docs/correctness.md
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
recreated actor. A caller authorized before destruction receives
|
|
13
13
|
`ActorDestroyed`; an unknown or forged reference remains unauthorized.
|
|
14
14
|
- Graceful process shutdown and stale-process cleanup use the same atomic
|
|
15
|
-
ownership release
|
|
16
|
-
|
|
17
|
-
available again. A stale
|
|
18
|
-
process.
|
|
19
|
-
- Permanent operation failure raises `MessageFailed
|
|
20
|
-
ID and persisted error details
|
|
15
|
+
ownership release. Claimed messages return to ready membership. The runtime
|
|
16
|
+
unfences the activations. Effect, reminder, and broadcast claims become
|
|
17
|
+
available again. A stale process that drains is recoverable like a stale
|
|
18
|
+
process that runs.
|
|
19
|
+
- Permanent operation failure raises `MessageFailed`. The error carries the
|
|
20
|
+
durable message ID and the persisted error details. Actor code text is not the
|
|
21
21
|
public exception contract.
|
|
22
22
|
- Operation, lifecycle, observable, and payload callbacks retain their owning
|
|
23
23
|
runtime through async context. Isolated runtimes therefore never fall back to
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
- Activation passes are bounded. Yielding changes ready-membership polling
|
|
32
32
|
order only; it neither changes durable message sequence nor makes future work
|
|
33
33
|
due early.
|
|
34
|
-
- Idle hydrated actors
|
|
35
|
-
never bypasses claim membership or the commit fence
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
- Idle hydrated actors keep the same renewable lease as their fence. Cache reuse
|
|
35
|
+
never bypasses claim membership or the commit fence. A failed turn restores
|
|
36
|
+
its public fields before reuse. Conditional release cannot clear a newer owner
|
|
37
|
+
or generation.
|
|
38
38
|
- Actor setup completes before an attempt begins. A hydration, migration, or
|
|
39
|
-
activation failure
|
|
40
|
-
|
|
41
|
-
the setup error.
|
|
39
|
+
activation failure restores ready membership, releases its activation fence,
|
|
40
|
+
and restores the attempt count. All three happen in one atomic step. A caller
|
|
41
|
+
that waits receives the setup error.
|
|
42
42
|
- `guardApplicationDatabase()` rejects direct application writes during actor
|
|
43
43
|
operations, observable and payload projections, and state migrations. It
|
|
44
44
|
permits only `SELECT` through row-returning methods. Commit actions remain in
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
successful snapshots and their nested JSON values are frozen copies.
|
|
50
50
|
- Personalized payloads hydrate committed state separately for every payload
|
|
51
51
|
name and subscriber. Each projection is read-only, size bounded, and fenced
|
|
52
|
-
independently by actor incarnation and revision. One
|
|
53
|
-
|
|
54
|
-
change on an actor
|
|
55
|
-
|
|
52
|
+
independently by actor incarnation and revision. One projection that is
|
|
53
|
+
denied, that mutates, or that fails cannot stop its siblings or observable
|
|
54
|
+
delivery. A state change on an actor with payloads creates a revision
|
|
55
|
+
broadcast, even when that actor declares no scalar observables.
|
|
56
56
|
|
|
57
57
|
## Limitations and non-goals
|
|
58
58
|
|
|
@@ -71,6 +71,19 @@
|
|
|
71
71
|
placement, capacity, database backups, and database failover.
|
|
72
72
|
- Redis and PostgreSQL notifications reduce wake-up latency but do not replace
|
|
73
73
|
durable polling or become a source of truth.
|
|
74
|
+
- The browser platform has no `AsyncLocalStorage`. Its context store covers
|
|
75
|
+
only the synchronous part of a callback. After the first `await` inside an
|
|
76
|
+
actor operation, the ambient guards (`applicationWritesForbidden()` and the
|
|
77
|
+
inside-transaction check) read as unset. Durable-state fencing, mailbox
|
|
78
|
+
ordering, and the SQLite WASM deadline enforcement do not depend on those
|
|
79
|
+
guards. The guards are best-effort in the browser and exact in Node.
|
|
80
|
+
- `snapshotWithIncarnation`'s `createdAtMs` orders actor incarnations to the
|
|
81
|
+
millisecond. Every adapter stores `created_at_ms` at that same precision. If
|
|
82
|
+
you destroy and recreate the same actor identity inside one database-clock
|
|
83
|
+
millisecond, the two incarnations get an equal `createdAtMs`. In that narrow
|
|
84
|
+
case, a caller cannot tell which of the two is current, so it cannot fence a
|
|
85
|
+
derived write on that value. `instanceId` still changes and shows that a
|
|
86
|
+
recreation occurred. It is a random UUID and carries no order of its own.
|
|
74
87
|
- Large documents, bulk pipelines, globally placed edge state, and global
|
|
75
88
|
counters are outside the intended workload. Prefer an ordinary row
|
|
76
89
|
transaction when it completely enforces the invariant.
|
package/docs/dashboard.md
CHANGED
|
@@ -115,9 +115,9 @@ with fresh random bytes, so tokens differ between requests while every form
|
|
|
115
115
|
already open in the same session remains valid. POST requests without a valid
|
|
116
116
|
token receive 403 and do not perform the action.
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
private and
|
|
118
|
+
The dashboard escapes every stored or request-derived string before that string
|
|
119
|
+
enters the HTML. This includes the JSON in chart attributes. HTML and statistics
|
|
120
|
+
responses are private, and no cache holds them. The dashboard sends a nonce-backed content security
|
|
121
121
|
policy, denies framing, disables MIME sniffing, and limits referrers to the
|
|
122
122
|
same origin.
|
|
123
123
|
|
|
@@ -137,9 +137,9 @@ effects, broadcasts, and dead letters. Pause prevents workers from claiming
|
|
|
137
137
|
new turns for that identity; a turn already executing may still commit. Resume
|
|
138
138
|
clears the brake and normal polling resumes delivery.
|
|
139
139
|
|
|
140
|
-
Dead-letter retry calls `runtime.deadLetters.retry()
|
|
141
|
-
idempotency and actor-operation validation
|
|
142
|
-
|
|
140
|
+
Dead-letter retry calls `runtime.deadLetters.retry()`. It keeps the durable
|
|
141
|
+
idempotency and the actor-operation validation of that method. If the runtime
|
|
142
|
+
refuses a retry, the detail page shows it with status 422.
|
|
143
143
|
|
|
144
144
|
`HEAD /` performs only a schema reachability query and creates no CSRF session
|
|
145
145
|
state. Use it for liveness checks instead of polling the full dashboard.
|
|
@@ -19,18 +19,19 @@ do not parse error messages.
|
|
|
19
19
|
| `PayloadTooLarge` | Arguments, state, result, snapshot getter, effect result, or personalized payload exceeded its configured limit. | Reduce the JSON value or deliberately raise the corresponding limit. |
|
|
20
20
|
| `SyncInsideTransaction` | A committed call or message wait would self-deadlock inside this adapter's transaction. | Finish the transaction first or stage actor-owned work through a commit action. |
|
|
21
21
|
|
|
22
|
-
`this.reject()` accepts
|
|
23
|
-
camelCase. An invalid code throws the non-retryable
|
|
24
|
-
operation fails on its first attempt and a
|
|
25
|
-
`MessageFailed
|
|
22
|
+
`this.reject()` accepts any code that matches `[A-Za-z_][A-Za-z0-9_]*`.
|
|
23
|
+
camelCase is valid. An invalid code throws the non-retryable
|
|
24
|
+
`InvalidRejectionCode`. The operation then fails on its first attempt, and a
|
|
25
|
+
synchronous caller receives `MessageFailed`. It does not wait through retry
|
|
26
|
+
backoff.
|
|
26
27
|
|
|
27
28
|
`MessageReference.status()`, `result()`, and `wait()` reauthorize the stored
|
|
28
29
|
operation. `result()` returns `undefined` while work is nonterminal, returns the
|
|
29
30
|
committed result when complete, and raises `Rejected` or `MessageFailed` for a
|
|
30
31
|
terminal refusal or failure. `wait()` blocks until the same terminal outcomes
|
|
31
|
-
or its deadline. A reference does not
|
|
32
|
-
send
|
|
33
|
-
|
|
32
|
+
or its deadline. A reference does not keep the authorization context of the
|
|
33
|
+
original send. Supply the context to each of these methods, so that the runtime
|
|
34
|
+
can reauthorize the stored operation.
|
|
34
35
|
|
|
35
36
|
## Definition and programming errors
|
|
36
37
|
|
|
@@ -97,8 +98,7 @@ linked replacement message; repeating the call returns the same replacement.
|
|
|
97
98
|
Effects are different: they execute outside the actor transaction and are at
|
|
98
99
|
least once. Deduplicate external work with the stable `EffectContext.id`.
|
|
99
100
|
Success and failure callback operations receive the originally staged
|
|
100
|
-
`arguments` for actor-state correlation. A retryable failure
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
without waiting.
|
|
101
|
+
`arguments` for actor-state correlation. A retryable failure with a future
|
|
102
|
+
schedule is correctly idle for the present pass, so `runtime.testing.drain()`
|
|
103
|
+
does not advance retry backoff. Use a `NonRetryableError` when a test must reach
|
|
104
|
+
the exhausted failure callback immediately.
|
package/docs/fit.md
CHANGED
|
@@ -14,6 +14,11 @@ or realtime projections.
|
|
|
14
14
|
invalidations atomically.
|
|
15
15
|
- The application already operates SQLite, PostgreSQL, or MySQL and should keep
|
|
16
16
|
durable coordination there.
|
|
17
|
+
- A local-first application needs the same actor model in the browser:
|
|
18
|
+
durable per-user state on SQLite WASM, one runtime shared across tabs, and
|
|
19
|
+
an outbox that syncs to a server when the network allows. The server side
|
|
20
|
+
can be a Node runtime or a Rails application through the Ruby gem's
|
|
21
|
+
transmit ingest; the wire contract is shared.
|
|
17
22
|
|
|
18
23
|
## Prefer a row transaction when
|
|
19
24
|
|
package/docs/operations.md
CHANGED
|
@@ -13,9 +13,9 @@ not cross a process boundary. When live processes share the database without a
|
|
|
13
13
|
configured adapter, the runtime logs
|
|
14
14
|
`solid_objects.polling_only_cross_process_wake_up` once. Use PostgreSQL
|
|
15
15
|
notifications or optional Redis Pub/Sub when separate processes need prompt
|
|
16
|
-
delivery
|
|
17
|
-
polling interval.
|
|
18
|
-
|
|
16
|
+
delivery. Without one of them, newly committed work can wait for the current
|
|
17
|
+
idle polling interval. The runtime isolates notification errors and logs them by
|
|
18
|
+
role and error class. The committed work does not fail.
|
|
19
19
|
|
|
20
20
|
The warning excludes process rows with the current hostname and host process ID.
|
|
21
21
|
It can therefore appear during a rolling deployment or restart overlap when an
|
|
@@ -53,8 +53,8 @@ isolated backlogs. `solid_objects.activation.yielded` reports the actor
|
|
|
53
53
|
identity, turns processed, and remaining due membership count.
|
|
54
54
|
|
|
55
55
|
`claimScanLimit` defaults to 100. Global claims inspect a bounded ordered set of
|
|
56
|
-
actor identities
|
|
57
|
-
|
|
56
|
+
actor identities. They continue after a lost lease race. Worker parallelism
|
|
57
|
+
stays, and the scan stays bounded.
|
|
58
58
|
|
|
59
59
|
Workers retain a hydrated actor and its fenced lease for
|
|
60
60
|
`idleDeactivationTimeoutMilliseconds`, which defaults to 30 seconds. Idle
|
|
@@ -66,15 +66,17 @@ longer polling.
|
|
|
66
66
|
|
|
67
67
|
Actors can override protected `onActivate()` and `onDeactivate()` methods for
|
|
68
68
|
nondurable, process-local resources. Either hook may be asynchronous. Hook code
|
|
69
|
-
runs under the application-write guard
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
runs under the application-write guard. `onDeactivate()` is best effort:
|
|
70
|
+
|
|
71
|
+
- it may not run after a crash;
|
|
72
|
+
- it cannot establish a correctness guarantee;
|
|
73
|
+
- the runtime logs a failure and still releases the lease.
|
|
72
74
|
|
|
73
75
|
`runtime.administration.processes()` returns the same administration-authorized
|
|
74
76
|
immutable process metadata as `runtime.processes.all()`, with hostname, host
|
|
75
|
-
process ID, Node and Solid Objects versions, and a current `stale` flag.
|
|
76
|
-
|
|
77
|
-
the query
|
|
77
|
+
process ID, Node and Solid Objects versions, and a current `stale` flag. You can
|
|
78
|
+
safely call it through the runtime's database adapter while the workers run. The
|
|
79
|
+
runtime serializes the query with the other database access, and it needs no
|
|
78
80
|
second SQLite connection. Graceful shutdown first persists `draining` with a
|
|
79
81
|
`shutdownRequestedAt` timestamp, then deactivates owned actors and atomically
|
|
80
82
|
releases every role claim before persisting `stopped`. `cleanup()` reauthorizes
|
|
@@ -82,7 +84,8 @@ separately and performs the same release for stale running or draining
|
|
|
82
84
|
processes. The cleanup count is instrumented; application payloads are not.
|
|
83
85
|
|
|
84
86
|
Committed calls and `message.wait()` apply `timeoutMilliseconds` to the entire
|
|
85
|
-
durable wait
|
|
87
|
+
durable wait. The clock starts before enqueue or message lookup. Adapter
|
|
88
|
+
deadlines
|
|
86
89
|
bound serialized SQLite access and lock waits, PostgreSQL pool acquisition,
|
|
87
90
|
statements, and locks, and MySQL pool acquisition, queries, and transaction
|
|
88
91
|
lock waits. A `SyncEnqueueTimeout` means the enqueue transaction did not commit
|
|
@@ -112,10 +115,14 @@ ID and later calls return a reference to that same message.
|
|
|
112
115
|
|
|
113
116
|
Self-scheduling actors need a low-frequency reconciler because application
|
|
114
117
|
alarms can still be lost. `runtime.reconciliation` provides administration-
|
|
115
|
-
authorized, read-only views for
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
authorized, read-only views for:
|
|
119
|
+
|
|
120
|
+
- active instances;
|
|
121
|
+
- quiet instances with no ready work, claimed work, or scheduled reminder;
|
|
122
|
+
- migrated state batches;
|
|
123
|
+
- orphaned actor IDs.
|
|
124
|
+
|
|
125
|
+
Collection reads use a maximum page size of 1,000 and a stable cursor.
|
|
119
126
|
|
|
120
127
|
The host application supplies its current owner IDs to `orphaned()` because
|
|
121
128
|
Node applications do not share an Active Record relation abstraction. Send
|
|
@@ -140,9 +147,16 @@ actor type appears in `instanceRetentionByActorType`, and remains an explicit
|
|
|
140
147
|
operator action because it deletes the entire actor incarnation.
|
|
141
148
|
|
|
142
149
|
Pruning selects and rechecks at most `pruneBatchSize` rows per transaction. It
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
keeps:
|
|
151
|
+
|
|
152
|
+
- ready and claimed messages;
|
|
153
|
+
- dead-letter originals and replacements;
|
|
154
|
+
- unfinished effects and broadcasts;
|
|
155
|
+
- scheduled reminders;
|
|
156
|
+
- leased or paused instances;
|
|
157
|
+
- processes that still own a claim or an activation.
|
|
158
|
+
|
|
159
|
+
Instance
|
|
146
160
|
expiration removes the entire actor incarnation and all of its retained
|
|
147
161
|
history, so use it only for actor types whose state is safely disposable.
|
|
148
162
|
|