solid-objects 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +1 -0
  3. package/dist/actor-runtime.d.ts +7 -0
  4. package/dist/actor-runtime.d.ts.map +1 -1
  5. package/dist/actor.d.ts +1 -0
  6. package/dist/actor.d.ts.map +1 -1
  7. package/dist/actor.js +3 -0
  8. package/dist/actor.js.map +1 -1
  9. package/dist/cloudflare/engine.d.ts +1 -0
  10. package/dist/cloudflare/engine.d.ts.map +1 -1
  11. package/dist/cloudflare/engine.js +118 -15
  12. package/dist/cloudflare/engine.js.map +1 -1
  13. package/dist/cloudflare/records.d.ts +2 -0
  14. package/dist/cloudflare/records.d.ts.map +1 -1
  15. package/dist/cloudflare/runtime.d.ts +7 -0
  16. package/dist/cloudflare/runtime.d.ts.map +1 -1
  17. package/dist/cloudflare/runtime.js +13 -0
  18. package/dist/cloudflare/runtime.js.map +1 -1
  19. package/dist/configuration.d.ts +10 -0
  20. package/dist/configuration.d.ts.map +1 -1
  21. package/dist/configuration.js.map +1 -1
  22. package/dist/diagnostics.d.ts +37 -0
  23. package/dist/diagnostics.d.ts.map +1 -0
  24. package/dist/diagnostics.js +73 -0
  25. package/dist/diagnostics.js.map +1 -0
  26. package/dist/doctor.js +1 -1
  27. package/dist/doctor.js.map +1 -1
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/realtime.d.ts.map +1 -1
  31. package/dist/realtime.js +14 -1
  32. package/dist/realtime.js.map +1 -1
  33. package/dist/reference.d.ts +11 -0
  34. package/dist/reference.d.ts.map +1 -1
  35. package/dist/reference.js +22 -0
  36. package/dist/reference.js.map +1 -1
  37. package/dist/repository.d.ts +1 -1
  38. package/dist/repository.d.ts.map +1 -1
  39. package/dist/repository.js +7 -6
  40. package/dist/repository.js.map +1 -1
  41. package/dist/runtime.d.ts +8 -0
  42. package/dist/runtime.d.ts.map +1 -1
  43. package/dist/runtime.js +108 -32
  44. package/dist/runtime.js.map +1 -1
  45. package/dist/schema.d.ts.map +1 -1
  46. package/dist/schema.js +20 -5
  47. package/dist/schema.js.map +1 -1
  48. package/dist/serialization.js +3 -5
  49. package/dist/serialization.js.map +1 -1
  50. package/dist/telemetry.d.ts +22 -0
  51. package/dist/telemetry.d.ts.map +1 -0
  52. package/dist/telemetry.js +126 -0
  53. package/dist/telemetry.js.map +1 -0
  54. package/dist/transmit.js +2 -2
  55. package/dist/transmit.js.map +1 -1
  56. package/dist/turn.js +2 -2
  57. package/dist/turn.js.map +1 -1
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/dist/wake-up-notification.d.ts.map +1 -1
  61. package/dist/wake-up-notification.js +9 -1
  62. package/dist/wake-up-notification.js.map +1 -1
  63. package/docs/api.md +25 -3
  64. package/docs/authorization.md +6 -0
  65. package/docs/observability.md +192 -0
  66. package/docs/parity.md +84 -72
  67. package/package.json +3 -3
package/docs/parity.md CHANGED
@@ -4,18 +4,15 @@ This ledger tracks capability parity with the Ruby `solid_objects` gem.
4
4
  Parity preserves a capability and its correctness or security boundary. It does
5
5
  not copy a Rails API into Node.
6
6
 
7
- Reference: Ruby `solid_objects` 0.14.0. The JavaScript package began at the
8
- Ruby design's `0.12` capability generation; that version number did not imply
9
- earlier JavaScript releases.
10
-
11
- The Node `0.14.0` implementation has capability parity with that reference. Its
12
- relational runtime, correctness boundaries, administration, diagnostics,
13
- operator dashboard, realtime projections, browser behavior, and supported
14
- adapters have native equivalents. Transport- and framework-neutral JavaScript
15
- APIs replace the Rails-specific render surfaces. Three rows below are explicit
16
- scope boundaries that the Ruby reference shares: the partial guard row, the
17
- backpressure row, and the shared planned result-lookup row. They are not missing
18
- Ruby capabilities.
7
+ Reference: Ruby `solid_objects` 0.17.0. The JavaScript package began at the Ruby
8
+ design's `0.12` capability generation; that version number did not imply earlier
9
+ JavaScript releases.
10
+
11
+ The SQL runtimes share durable identity, ordered mailboxes, fenced commits,
12
+ effects, reminders, authorization, administration, and realtime guarantees.
13
+ Transport- and framework-neutral JavaScript APIs replace Rails-specific surfaces.
14
+ The partial write-guard and backpressure rows below record their actual limits.
15
+ Browser and Cloudflare hosting have separate scope boundaries later in this file.
19
16
 
20
17
  `0.14.0` also adds `runtime.enqueueInternalMessage()`,
21
18
  `runtime.enqueueInternalMessageInTransaction()`, and
@@ -32,7 +29,7 @@ such boundary between a gem and its dependents.
32
29
  Operation-reference typing is runtime-specific: TypeScript infers scheduled and
33
30
  transmitted operations from the concrete receiver, and checks literal effect
34
31
  callback names. Ruby offers opt-in RBS generation from declared application types
35
- in [solid-objects-ruby#66](https://github.com/cardmagic/solid-objects-ruby/pull/66).
32
+ since Ruby 0.14.7.
36
33
  Both preserve runtime operation validation and global effect/commit-action names;
37
34
  this does not imply automatic TypeScript-style inference in Ruby.
38
35
 
@@ -51,60 +48,63 @@ SQL waiters read results and status from one statement. Ruby already checks
51
48
  completion and returns the result from the same loaded message; no Ruby change
52
49
  is needed for the JavaScript stale-result race fix.
53
50
 
54
- | Capability | Status | TypeScript shape or remaining work |
55
- | ------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
56
- | Actor registry, durable identity, JSON state, and adjacent state migrations | Native | Ordinary classes, static actor types, inferred state, explicit migrations, and isolated runtime context across every actor-instance callback. |
57
- | Fluent committed calls and background delivery | Native | `await reference.operation()` and `reference.send.operation()`. |
58
- | Ordered mailbox, sequence allocation, idempotency, retries, dead letters, leases, renewal, and fenced commits | Native | Relational ready/claimed membership tables, distinct generated request IDs and caller idempotency keys, durable history, adapter-appropriate sequence locking, and PostgreSQL/MySQL row locks held from fence validation through commit. |
59
- | Domain rejection and strict poison ordering | Native | Rejections accept JavaScript identifier-style codes and roll back without retry; invalid codes fail terminally, while retryable failures block later operations until completion or dead-lettering. |
60
- | Bounded activation passes and hot-actor fairness | Native | Configurable turn-count and elapsed-time budgets bound each pass, then move only that actor's already-due memberships behind actors already waiting. |
61
- | Bounded claim candidate scan | Native | A configurable ordered scan continues to another ready actor when a worker loses the first candidate's lease race. |
62
- | Backpressure and payload caps | Partial | Serialization enforces a shared maximum JSON nesting depth, raising `InvalidPayload`, and an optional caller-supplied `maxBytes` limit, raising `PayloadTooLarge`; reminder names are bounded to 255 characters. Distributed per-actor rate limits, global admission control, and cache-capacity eviction are not planned in either runtime. They are hot, request-path, and loss-tolerant, so one durable ordered message per check is the wrong shape. Solid Objects Pro answers them with grouped and ephemeral operations, which [fit](fit.md) describes. |
63
- | Idle activation cache | Native | Long-running workers retain hydrated actors under renewable fenced leases, restore public state after failed turns, and release on timeout, fairness yield, lease loss, or shutdown. |
64
- | Transactional effects and outcome operations | Native | At-least-once handlers receive immutable stable effect, attempt, source-message, and actor identity; success and failure operations also receive the originally staged arguments for correlation. Typed callback envelopes are exported. |
65
- | Actor-to-actor delivery | Native | `sendTo(reference).operation()` stages delivery in the source actor commit. |
66
- | One-shot and recurring reminders | Native | Scheduling, replacement events, catch-up policy, stale-claim recovery, pausing, authorized inspection, and idempotent resume are implemented. |
67
- | Same-database commit actions | Native | Registered actions receive source-message identity, mailbox sequence, activation generation, and the fenced transaction connection. |
68
- | Ambient transaction rejection | Native | Committed calls and message waits fail before blocking when the current async context already owns a transaction on the Solid Objects adapter. |
69
- | Direct application-write isolation during actor code | Partial | `guardApplicationDatabase()` fails closed for operations, projections, migrations, and commit actions; only the supplied fenced commit-action connection may write. Unwrapped clients cannot be intercepted. |
70
- | Committed snapshots | Native | `snapshot()` returns authorized persisted fields and inferred getters from one read-only committed state image; realtime replay reads explicit observables without mailbox history. |
71
- | Actor destruction and incarnation fencing | Native | Authorized cascading deletion creates a fresh instance ID on recreation; an authorized waiter receives `ActorDestroyed` when that incarnation disappears. |
72
- | Result recovery and sync timeout diagnostics | Native | Status, result, and wait reauthorize the stored operation; terminal failure raises structured `MessageFailed`; whole-call adapter deadlines distinguish enqueue, wait, database, activation, and mailbox blockers. |
73
- | Result lookup by request ID and idempotency key | Native | `runtime.findBy({ requestId })` and `reference.findBy({ idempotencyKey })` rebuild a `MessageReference`, authorized with the hook the original call ran. An actor remembers the keys of its own finished turns, so a key lookup separates a pruned message from one that never existed. |
51
+ | Capability | Status | TypeScript shape or remaining work |
52
+ | ------------------------------------------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53
+ | Actor registry, durable identity, JSON state, and adjacent state migrations | Native | Ordinary classes, static actor types, inferred state, explicit migrations, and isolated runtime context across every actor-instance callback. |
54
+ | Query and projection purity | Native | Both runtimes reject state mutation and staged effects, recovery checks, commit actions, reminders, or outbound messages in queries, observable projections, and personalized payloads. Projection guards compare the complete staged work, including same-count replacements. Query and observable violations fail terminally with `QueryMutatedState`; payload violations fail only that payload. Snapshot projection reads enforce the same rule. |
55
+ | JSON property preservation | Native | Reserved property names such as `__proto__`, `constructor`, and `prototype` remain ordinary data, including through size checks and immutable copies. Both suites consume `compatibility/json-values.json`. |
56
+ | Fluent committed calls and background delivery | Native | `await reference.operation()` and `reference.send.operation()`. |
57
+ | Ordered mailbox, sequence allocation, idempotency, retries, dead letters, leases, renewal, and fenced commits | Native | Relational ready/claimed membership tables, distinct generated request IDs and caller idempotency keys, durable history, adapter-appropriate sequence locking, and PostgreSQL/MySQL row locks held from fence validation through commit. |
58
+ | Domain rejection and strict poison ordering | Native | Rejections accept JavaScript identifier-style codes and roll back without retry; invalid codes fail terminally, while retryable failures block later operations until completion or dead-lettering. |
59
+ | Bounded activation passes and hot-actor fairness | Native | Configurable turn-count and elapsed-time budgets bound each pass, then move only that actor's already-due memberships behind actors already waiting. |
60
+ | Bounded claim candidate scan | Native | A configurable ordered scan continues to another ready actor when a worker loses the first candidate's lease race. |
61
+ | Backpressure and payload caps | Partial | Serialization enforces a shared maximum JSON nesting depth, raising `InvalidPayload`, and an optional caller-supplied `maxBytes` limit, raising `PayloadTooLarge`; keyed reminder names are bounded to 255 characters in JS and 191 in Ruby. These existing database limits remain runtime-specific; use at most 191 ASCII characters for a name shared across runtimes. Distributed per-actor rate limits, global admission control, and cache-capacity eviction are not planned in either runtime. They are hot, request-path, and loss-tolerant, so one durable ordered message per check is the wrong shape. Solid Objects Pro answers them with grouped and ephemeral operations, which [fit](fit.md) describes. |
62
+ | Idle activation cache | Native | Long-running workers retain hydrated actors under renewable fenced leases, restore public state after failed turns, and release on timeout, fairness yield, lease loss, or shutdown. |
63
+ | Transactional effects and outcome operations | Native | At-least-once handlers receive immutable stable effect, attempt, source-message, and actor identity; success and failure operations also receive the originally staged arguments for correlation. Typed callback envelopes are exported. |
64
+ | Actor-to-actor delivery | Native | `sendTo(reference).operation()` stages delivery in the source actor commit. |
65
+ | One-shot and recurring reminders | Native | Scheduling, cancellation by name/key/handle, read-your-writes inspection, catch-up policy, stale-claim recovery, pausing, and authorized resume. Cancellation cannot recall an already-enqueued occurrence. |
66
+ | Same-database commit actions | Native | Registered actions receive source-message identity, mailbox sequence, activation generation, and the fenced transaction connection. |
67
+ | Ambient transaction rejection | Native | Committed calls and message waits fail before blocking when the current async context already owns a transaction on the Solid Objects adapter. |
68
+ | Direct application-write isolation during actor code | Partial | `guardApplicationDatabase()` fails closed for operations, projections, migrations, and commit actions; only the supplied fenced commit-action connection may write. Unwrapped clients cannot be intercepted. |
69
+ | Committed snapshots | Native | `snapshot()` returns authorized persisted fields and inferred getters from one read-only committed state image; realtime replay reads explicit observables without mailbox history. |
70
+ | Actor destruction and incarnation fencing | Native | Authorized cascading deletion creates a fresh instance ID on recreation; an authorized waiter receives `ActorDestroyed` when that incarnation disappears. |
71
+ | Result recovery and sync timeout diagnostics | Native | Status, result, outcome, and wait reauthorize the original stored operation and arguments. All delivery modes retain immutable JSON results under the result-size cap. Result reads raise terminal rejection/failure errors; outcome returns them as data. Whole-call deadlines distinguish enqueue, wait, database, activation, and mailbox blockers. |
72
+ | Result lookup by request ID and idempotency key | Native | `runtime.findBy({ requestId })` and `reference.findBy({ idempotencyKey })` rebuild a `MessageReference`, authorized with the hook the original call ran. An actor remembers the keys of its own finished turns, so a key lookup separates a pruned message from one that never existed. |
74
73
 
75
74
  Effect callback envelopes are typed with `EffectFailurePayload`,
76
75
  `EffectSuccessPayload`, and `SerializedError` in both SQL and Cloudflare.
77
- Ruby RBS contracts are tracked in cardmagic/solid-objects-ruby#64 and preserve
78
- Ruby field names; this does not change runtime delivery semantics.
76
+ Ruby publishes RBS contracts with native Ruby field names; this does not change runtime delivery semantics.
79
77
 
80
78
  ## Operations
81
79
 
82
- | Capability | Status | TypeScript shape or remaining work |
83
- | ----------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
84
- | Process registration, heartbeats, stale claim recovery, and graceful shutdown | Native | Runtime roles persist host, PID, runtime versions, draining and stopped transitions, cooperative cancellation, and a bounded shutdown deadline; cleanup recovers stale claims. |
85
- | Failed-role replacement | Native | Built-in and registered roles are rebuilt through their factories with capped backoff; shutdown is the terminal replacement boundary. |
86
- | Additional supervised components | Native | `registerComponent()` builds, validates, runs, and stops application components with the runtime. |
87
- | Dead-letter inspection and retry | Native | `runtime.deadLetters` provides deny-by-default immutable inspection and idempotent durable retry linkage. |
88
- | Reconciliation reads | Native | Authorized cursor pages cover active, quiet, and orphaned instances; bounded state batches are migrated and deeply frozen. |
89
- | Message, process, and opt-in instance retention | Native | Supervised scheduling bounds message and process growth; authorized manual APIs add preview and keep destructive instance expiration explicit. SQL instance pruning uses an index on actor type and update time. |
90
- | Doctor and schema verification | Native | Structured checks cover configuration, schema/version shape, adapter server versions, neutral-context policy probes, live roles, and a targeted round trip. |
91
- | CLI | Native | The packaged executable loads an application runtime and exposes start, diagnostics, processes, dead letters, reminders, and explicit retention pruning as JSON. |
92
- | Operator dashboard | Native | The opt-in `solid-objects/web` export provides Fetch and Node/Connect mounting, authorized runtime views and actions, session-backed CSRF, filtering, paging, charts, and immutable extension hooks. Matches the Ruby dashboard's own documented limits: no audit trail of admin actions, dead-letter retry is one at a time, and pause sets a flag rather than interrupting an in-flight turn. |
93
- | Structured instrumentation | Native | An isolated transport-neutral sink emits immutable lifecycle metadata and structurally excludes application payloads. |
94
- | Large committed state warning | Native | `warnStateBytes` reports one `solid_objects.state.large` event, holding the actor type, actor ID, byte count, and threshold, when a committed image passes a 128 KB soft threshold. The event holds no application state, and it reports after the commit. The Ruby gem carries the same event and the same 5 MB hard default from `0.14.3`, as `warn_state_bytes`. Its threshold defaults to 64 KB rather than 128 KB, because its measured curve falls sooner: it keeps 55% of its empty-state throughput at 13 KB, where this package keeps 98% at 16 KB. |
95
- | Public test helper | Native | `runtime.testing` provides role-selective deterministic draining, explicit-time due-reminder execution, and dependency-ordered reset without relying on cascades. |
80
+ | Capability | Status | TypeScript shape or remaining work |
81
+ | ----------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
82
+ | Process registration, heartbeats, stale claim recovery, and graceful shutdown | Native | Runtime roles persist host, PID, runtime versions, draining and stopped transitions, cooperative cancellation, and a bounded shutdown deadline; cleanup recovers stale claims. |
83
+ | Failed-role replacement | Native | Built-in and registered roles are rebuilt through their factories with capped backoff; shutdown is the terminal replacement boundary. |
84
+ | Additional supervised components | Native | `registerComponent()` builds, validates, runs, and stops application components with the runtime. |
85
+ | Dead-letter inspection and retry | Native | `runtime.deadLetters` provides deny-by-default immutable inspection and idempotent durable retry linkage. |
86
+ | Reconciliation reads | Native | Authorized cursor pages cover active, quiet, and orphaned instances; bounded state batches are migrated and deeply frozen. |
87
+ | Message, process, and opt-in instance retention | Native | Supervised scheduling bounds message and process growth; authorized manual APIs add preview and keep destructive instance expiration explicit. SQL instance pruning uses an index on actor type and update time. |
88
+ | Doctor and schema verification | Native | Structured checks cover configuration, schema/version shape, adapter server versions, neutral-context policy probes, live roles, and a targeted round trip. |
89
+ | CLI | Native | The packaged executable loads an application runtime and exposes start, diagnostics, processes, dead letters, reminders, and explicit retention pruning as JSON. |
90
+ | Operator dashboard | Native | Fetch and Node/Connect mounting, authorized views and actions, CSRF, filtering, paging, charts, and extensions match the Rack dashboard. Both dashboards expose individual retries and pause/resume; bulk redrive is available through the administration APIs. Pause does not interrupt an in-flight turn. |
91
+ | Bulk redrive and administration audit | Native | Message, effect, and broadcast scopes support idempotent retry and durable, bounded, cancellable redrive. Retry and task transitions persist administration identity in audit rows. |
92
+ | Structured instrumentation | Native | Versioned events, metric samples, isolated observers, and bounded authorized diagnostics match Ruby. Both test suites check SQL event attributes against `compatibility/telemetry-events.json`; only JS activation events add their turn's message fields. Both runtimes log exporter and observer failures, require observer callbacks, and cap local observers at 1,000. Timeout events share wait reasons, activation owner IDs, and string generations; see [observability](observability.md). Durable Objects uses host instrumentation and remote diagnostics, sends fewer attributes, and does not send `sync.timeout`. |
93
+ | Large committed state warning | Native | `warnStateBytes` reports one `solid_objects.state.large` event, holding the actor type, actor ID, byte count, and threshold, when a committed image passes a 128 KB soft threshold. The event holds no application state, and it reports after the commit. The Ruby gem carries the same event and the same 5 MB hard default from `0.14.3`, as `warn_state_bytes`. Its threshold defaults to 64 KB rather than 128 KB, because its measured curve falls sooner: it keeps 55% of its empty-state throughput at 13 KB, where this package keeps 98% at 16 KB. |
94
+ | Public test helper | Native | `runtime.testing` provides role-selective deterministic draining, explicit-time due-reminder execution, and dependency-ordered reset without relying on cascades. |
96
95
 
97
96
  ## Databases and wake-up
98
97
 
99
- | Capability | Status | TypeScript shape or remaining work |
100
- | ------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
101
- | SQLite | Native | Uses built-in `node:sqlite`, serialized process-local access, bounded transient writer retries, foreign keys, strict tables, database time, and deadline-bounded access and lock waits. |
102
- | PostgreSQL | Native | Optional `pg` 8.23 peer, bounded pooling, 64-bit schema, row-locked sequences, server checks, and deadline-bounded pool, statement, and lock waits. |
103
- | MySQL | Native | Optional `mysql2` 3.23 peer, bounded pooling, InnoDB schema, row-locked sequences, scoped deadlock retry, and deadline-bounded pool, query, and lock waits. Ruby also tests a second client, `trilogy`; Node has no comparable second MySQL client, so only `mysql2` is tracked here. |
104
- | Durable polling fallback | Native | Every role progresses without a notification service. Effects, reminders, and broadcasts use canonical ordered polling indexes; PostgreSQL and MySQL lock only the selected row with `FOR UPDATE SKIP LOCKED`. Broadcasts and reminders compare separate available and stale-recovery probes, preserve the oldest-first choice, and retry past candidates locked by another claimant. |
105
- | In-process wake-up | Native | A generation-based default adapter prevents claim-to-wait signal loss; commits wake role-specific waiters and polling remains the fallback. |
106
- | PostgreSQL wake-up | Native | `database.wakeUp()` uses one dedicated event-driven client, role-specific `LISTEN/NOTIFY`, generation fencing, reconnectable listeners, and durable polling fallback. |
107
- | Redis wake-up | Native | An optional `redis` peer provides role-specific Pub/Sub over separate lazy publisher/subscriber connections, with bounded failures and durable polling fallback. |
98
+ | Capability | Status | TypeScript shape or remaining work |
99
+ | --------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
+ | SQLite | Native | Uses built-in `node:sqlite`, serialized process-local access, bounded transient writer retries, foreign keys, strict tables, database time, and deadline-bounded access and lock waits. Ruby uses a yielding busy handler for background writes so a waiting thread lets the lock holder commit, including on Rails 7.1/7.2; both preserve synchronous deadlines. |
101
+ | PostgreSQL | Native | Optional `pg` 8.23 peer, bounded pooling, 64-bit schema, row-locked sequences, server checks, and deadline-bounded pool, statement, and lock waits. |
102
+ | MySQL | Native | Optional `mysql2` 3.23 peer, bounded pooling, InnoDB schema, row-locked sequences, scoped deadlock retry, and deadline-bounded pool, query, and lock waits. Ruby also tests a second client, `trilogy`; Node has no comparable second MySQL client, so only `mysql2` is tracked here. |
103
+ | Durable polling fallback | Native | Every role progresses without a notification service. Effects, reminders, and broadcasts use canonical ordered polling indexes; PostgreSQL and MySQL lock only the selected row with `FOR UPDATE SKIP LOCKED`. Broadcasts and reminders compare separate available and stale-recovery probes, preserve the oldest-first choice, and retry past candidates locked by another claimant. |
104
+ | In-process wake-up | Native | A generation-based adapter prevents claim-to-wait signal loss. It is the polling fallback or an explicit opt-out from automatic cross-process selection. |
105
+ | Automatic wake-up selection | Native | Prefer configured Redis, then proven PostgreSQL notifications, then polling. Probe failures warn and report the resolved capability; MySQL needs Redis for cross-process wake-up. |
106
+ | PostgreSQL wake-up | Native | `database.wakeUp()` uses one dedicated event-driven client, role-specific `LISTEN/NOTIFY`, generation fencing, reconnectable listeners, and durable polling fallback. |
107
+ | Redis wake-up | Native | An optional `redis` peer provides role-specific Pub/Sub over separate lazy publisher/subscriber connections, with bounded failures and durable polling fallback. |
108
108
 
109
109
  Both runtimes select a wake-up adapter automatically. `wakeUp` takes a name or
110
110
  an adapter and defaults to `"automatic"`, which prefers a configured Redis URL,
@@ -152,11 +152,14 @@ with the hook the original call ran, and report absence, an unregistered actor,
152
152
  and a refusal the same way. `outcome` reports the status, result, error,
153
153
  rejection, and attempt count in both.
154
154
 
155
- Three details differ, and all come from the runtimes rather than the feature.
156
- This runtime stores a result for every completed message, so a lookup answers
157
- one for asynchronous work; Ruby stores a result only for `sync` delivery, so a
158
- lookup there answers the status and the error but not the result. This runtime
159
- needed a new unique index on `request_id`, added as schema version 12, because
155
+ Both runtimes retain JSON results for synchronous, background, and internal
156
+ messages under the configured result-size cap. Every status, result, and outcome
157
+ read rechecks authorization against the original operation and arguments.
158
+ `result` raises terminal failures; `outcome` exposes them as data. Results discarded
159
+ by older Ruby workers cannot be recovered retroactively.
160
+
161
+ Two storage details differ. This runtime needed a new unique index on
162
+ `request_id`, added as schema version 12, because
160
163
  its table constrained the pair `(actor_type, actor_id, request_id)`; the Ruby
161
164
  schema has carried a global unique index since its first migration. The error
162
165
  record also differs: Ruby's `ErrorRecord` carries `class_name`, `message`, and
@@ -189,15 +192,15 @@ because a Durable Object indexes only its own messages, so that form raises
189
192
 
190
193
  ## Realtime and browser behavior
191
194
 
192
- | Capability | Status | TypeScript shape or remaining work |
193
- | ------------------------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
194
- | Explicit observable projection and durable invalidations | Native | `observables()` is opt-in and invalidation-only by default. `broadcastValue()` sends changed values; `broadcastInvalidation()` explicitly sends only changed names while comparing the real value. Private or subscriber-specific values belong behind invalidation-only component endpoints or in typed payloads. |
195
- | Action Cable channels and signed stream names | Not applicable | `runtime.realtime` provides authenticated transport-neutral sessions; the host owns its HTTP/WebSocket server and authentication. |
196
- | Authorized subscriptions | Native | Each request is denied by default and authorized before actor lookup; sessions replay committed observables and fence ordered durable revisions. Multi-process hosts explicitly bridge their shared transport. |
197
- | Turbo scalar replacement | Not applicable | The browser client exposes invalidations to application rendering code. Framework adapters can be separate packages. |
198
- | Keyed component refresh, morph/replace, and batch coalescing | Native | A typed framework-neutral registry selects explicit dependencies, coalesces batch requests, aborts superseded work, fences each target, and delegates synchronous application strategy to the host. |
199
- | Personalized payload broadcasts | Native | Static typed projections run against committed state under each fresh subscriber context, reauthorize as queries, isolate failures, and carry independent revision fences. |
200
- | Real-browser compatibility suite | Native | Playwright exercises subscription replay over native WebSocket, incarnation/revision fences, payload delivery, component batching, and cancellation in Chromium. |
195
+ | Capability | Status | TypeScript shape or remaining work |
196
+ | ------------------------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
197
+ | Explicit observable projection and durable invalidations | Native | `observables()` is opt-in and invalidation-only by default. `broadcastValue()` sends changed values; `broadcastInvalidation()` explicitly sends only changed names while comparing the real value. Private or subscriber-specific values belong behind invalidation-only component endpoints or in typed payloads. |
198
+ | Action Cable channels and signed stream names | Not applicable | `runtime.realtime` provides authenticated transport-neutral sessions; the host owns its HTTP/WebSocket server and authentication. |
199
+ | Authorized subscriptions | Native | Each request is denied by default and authorized before actor lookup; sessions replay committed observables and fence ordered durable revisions. Multi-process hosts explicitly bridge their shared transport. |
200
+ | Turbo scalar replacement | Not applicable | The browser client exposes invalidations to application rendering code. Framework adapters can be separate packages. |
201
+ | Keyed component refresh, morph/replace, and batch coalescing | Native | A typed framework-neutral registry selects explicit dependencies, coalesces batch requests, aborts superseded work, fences each target, and delegates synchronous application strategy to the host. |
202
+ | Personalized payload broadcasts | Native | Both runtimes evaluate each personalized payload on an isolated actor from the same committed snapshot, including generated defaults, reject state mutation and staged work, prevent application database writes, and enforce the configured payload byte limit. They reauthorize as queries, isolate failures, and carry independent revision fences. |
203
+ | Real-browser compatibility suite | Native | Playwright exercises subscription replay over native WebSocket, incarnation/revision fences, payload delivery, component batching, and cancellation in Chromium. |
201
204
 
202
205
  ## JavaScript-only: the browser runtime
203
206
 
@@ -257,6 +260,8 @@ and [#48](https://github.com/cardmagic/solid-objects-ruby/issues/48)):
257
260
  with `register_transmit` is the staging side. Identifiers differ by
258
261
  runtime idiom, but both sides guarantee the same wire contract:
259
262
 
263
+ - transmit siblings preserve staging order within each source message, including retries;
264
+ - explicit `arguments: null` is invalid; only omission defaults to an empty object;
260
265
  - envelope keys are camelCase (`effectId`, `actorType`, `actorId`,
261
266
  `operation`, and an optional `arguments` that defaults to an empty
262
267
  object);
@@ -264,6 +269,13 @@ runtime idiom, but both sides guarantee the same wire contract:
264
269
  - a replay with changed arguments raises the idempotency conflict on both
265
270
  sides and leaves the first application intact.
266
271
 
272
+ JavaScript schema migration 14 adds an effect position for ordering within a
273
+ source message. Ruby uses its existing sequential effect primary key. Existing
274
+ JS rows receive position zero and retain their earlier ID-based tie-break;
275
+ their original staging order cannot be reconstructed. Run `runtime.install()`
276
+ before starting upgraded workers. The stronger ordering applies to effects
277
+ staged by upgraded workers.
278
+
267
279
  `compatibility/transmit-envelopes.json` is committed to both repositories
268
280
  with a consuming test on each side, so the contract is enforced from both
269
281
  sides of the repository boundary. Manual cross-runtime QA (Node to Rails
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid-objects",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Race-free realtime state per application identity, backed by your SQL database",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -115,8 +115,8 @@
115
115
  "test": "vitest run",
116
116
  "test:browser": "pnpm run build && playwright test",
117
117
  "test:coverage": "vitest run --coverage",
118
- "test:postgresql": "vitest run test/postgresql.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts",
119
- "test:mysql": "vitest run test/mysql.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts",
118
+ "test:postgresql": "vitest run test/postgresql.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts test/transmit-ordering.test.ts",
119
+ "test:mysql": "vitest run test/mysql.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts test/transmit-ordering.test.ts",
120
120
  "test:package": "node scripts/release-artifact-smoke.mjs",
121
121
  "test:recovery": "pnpm run build && node examples/failure-recovery/demo.ts",
122
122
  "test:at-least-once": "pnpm run build && node examples/at-least-once/demo.ts",