solid-objects 0.17.7 → 1.0.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 +60 -0
  2. package/README.md +23 -7
  3. package/dist/actor.d.ts.map +1 -1
  4. package/dist/actor.js +24 -2
  5. package/dist/actor.js.map +1 -1
  6. package/dist/broadcast-worker.d.ts.map +1 -1
  7. package/dist/broadcast-worker.js +5 -3
  8. package/dist/broadcast-worker.js.map +1 -1
  9. package/dist/cloudflare/protocol.d.ts.map +1 -1
  10. package/dist/cloudflare/protocol.js +1 -0
  11. package/dist/cloudflare/protocol.js.map +1 -1
  12. package/dist/effect-worker.d.ts.map +1 -1
  13. package/dist/effect-worker.js +5 -3
  14. package/dist/effect-worker.js.map +1 -1
  15. package/dist/errors.d.ts +2 -0
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +2 -0
  18. package/dist/errors.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/platform/node.js +2 -0
  24. package/dist/platform/node.js.map +1 -1
  25. package/dist/reminder-scheduler.d.ts.map +1 -1
  26. package/dist/reminder-scheduler.js +5 -3
  27. package/dist/reminder-scheduler.js.map +1 -1
  28. package/dist/repository.d.ts +1 -0
  29. package/dist/repository.d.ts.map +1 -1
  30. package/dist/repository.js +22 -2
  31. package/dist/repository.js.map +1 -1
  32. package/dist/runtime.d.ts +4 -1
  33. package/dist/runtime.d.ts.map +1 -1
  34. package/dist/runtime.js +58 -37
  35. package/dist/runtime.js.map +1 -1
  36. package/dist/test-helper.d.ts.map +1 -1
  37. package/dist/test-helper.js +8 -2
  38. package/dist/test-helper.js.map +1 -1
  39. package/dist/turn.d.ts.map +1 -1
  40. package/dist/turn.js +1 -0
  41. package/dist/turn.js.map +1 -1
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.d.ts.map +1 -1
  44. package/dist/version.js +1 -1
  45. package/dist/version.js.map +1 -1
  46. package/dist/worker.d.ts.map +1 -1
  47. package/dist/worker.js +5 -3
  48. package/dist/worker.js.map +1 -1
  49. package/dist/yield-to-event-loop.d.ts +3 -0
  50. package/dist/yield-to-event-loop.d.ts.map +1 -0
  51. package/dist/yield-to-event-loop.js +10 -0
  52. package/dist/yield-to-event-loop.js.map +1 -0
  53. package/docs/agents.md +16 -11
  54. package/docs/api.md +23 -3
  55. package/docs/configuration.md +24 -2
  56. package/docs/errors-and-recovery.md +2 -0
  57. package/docs/guides/persistent-rooms.md +1 -1
  58. package/docs/guides/race-conditions.md +1 -1
  59. package/docs/observability.md +16 -15
  60. package/docs/operations.md +2 -1
  61. package/docs/parity.md +41 -39
  62. package/docs/versioning.md +50 -0
  63. package/docs/virtual-actors.md +15 -6
  64. package/examples/failure-recovery/demo.ts +6 -5
  65. package/examples/failure-recovery/process-messages.ts +10 -0
  66. package/examples/failure-recovery/worker.ts +2 -5
  67. package/package.json +8 -5
package/docs/parity.md CHANGED
@@ -4,7 +4,7 @@ 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.17.4. The JavaScript package began at the Ruby
7
+ Reference: Ruby `solid_objects` 1.0.0. The JavaScript package began at the Ruby
8
8
  design's `0.12` capability generation; that version number did not imply earlier
9
9
  JavaScript releases.
10
10
 
@@ -48,28 +48,29 @@ SQL waiters read results and status from one statement. Ruby already checks
48
48
  completion and returns the result from the same loaded message; no Ruby change
49
49
  is needed for the JavaScript stale-result race fix.
50
50
 
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 | Both runtimes renew between turns when due and retain the fence while a claim remains, allowing expired-turn recovery in mailbox order even with one allowed attempt. Reclaiming increments the attempt count; lease loss alone does not dead-letter a message. 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. |
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 | Both runtimes renew between turns when due and retain the fence while a claim remains, allowing expired-turn recovery in mailbox order even with one allowed attempt. Reclaiming increments the attempt count; lease loss alone does not dead-letter a message. Recovery after attempt one is immediate; subsequent interruptions use the existing retry delay with total attempts minus one, stored in ready membership. Mailbox order and other-actor progress are preserved. Both runtimes emit `activation.lost` with the message ID and attempt, including losses while saving a handler failure or rejection. JS busy workers and drains yield to timers and input between passes; Ruby uses threads and waits after lost turns. 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. Overlapping synchronous caller passes own separate workers, preserving staged intents and concurrent execution across actors; Ruby already isolates each invocation's activation. |
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
+ | Schedule call without an operation | Native | Both runtimes fail a turn that calls `schedule()` and then calls no operation on the result. The error is `ScheduleWithoutOperation`, which is not retried: the message becomes a dead letter, and the turn commits no state and no reminder. The turn can keep the result and call the operation later in the same turn. The projection guard counts such a call in an observable as staged work, so that turn fails with `QueryMutatedState`. The Cloudflare backend applies the same rules. |
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, 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. |
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. |
73
74
 
74
75
  Effect callback envelopes are typed with `EffectFailurePayload`,
75
76
  `EffectSuccessPayload`, and `SerializedError` in both SQL and Cloudflare.
@@ -77,22 +78,23 @@ Ruby publishes RBS contracts with native Ruby field names; this does not change
77
78
 
78
79
  ## Operations
79
80
 
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. |
95
- | Clean-install artifact proof | Native | `pnpm run test:package` installs the packed tarball in a new project. It runs the packaged quickstart, which sends 25 concurrent calls to one identity, and the `examples/ticket-sale.ts` guide example. `pnpm run test:recovery` proves recovery after a worker process stops. Ruby's `rake quickstart` builds the gem, installs it in a new Rails application, and proves ordering and reminder recovery after a runtime restart. |
81
+ | Capability | Status | TypeScript shape or remaining work |
82
+ | ----------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
83
+ | 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. |
84
+ | Failed-role replacement | Native | Built-in and registered roles are rebuilt through their factories with capped backoff; shutdown is the terminal replacement boundary. |
85
+ | Additional supervised components | Native | `registerComponent()` builds, validates, runs, and stops application components with the runtime. |
86
+ | Dead-letter inspection and retry | Native | `runtime.deadLetters` provides deny-by-default immutable inspection and idempotent durable retry linkage. |
87
+ | Reconciliation reads | Native | Authorized cursor pages cover active, quiet, and orphaned instances; bounded state batches are migrated and deeply frozen. |
88
+ | 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. Reused JS workers refresh their registration before each pass after cleanup or pruning; Ruby callers already reload and replace stale registrations. SQL instance pruning uses an index on actor type and update time. |
89
+ | 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. |
90
+ | `json` gem compatibility check | Not applicable | The Ruby doctor and the Rails boot decode one JSON document with Active Support, to find a `json` gem release that Active Support cannot use. Node.js has `JSON` built in, so this package has no such dependency. |
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 | 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. |
93
+ | 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. |
94
+ | 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`. |
95
+ | 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. |
96
+ | Public test helper | Native | `runtime.testing` provides role-selective deterministic draining, explicit-time due-reminder execution, and dependency-ordered reset without relying on cascades. |
97
+ | Clean-install artifact proof | Native | `pnpm run test:package` installs the packed tarball in a new project. It runs the packaged quickstart, which sends 25 concurrent calls to one identity, and the `examples/ticket-sale.ts` guide example. `pnpm run test:recovery` proves recovery after a worker process stops. Ruby's `rake quickstart` builds the gem, installs it in a new Rails application, and proves ordering and reminder recovery after a runtime restart. |
96
98
 
97
99
  ## Databases and wake-up
98
100
 
@@ -0,0 +1,50 @@
1
+ # Versioning
2
+
3
+ From version 1.0.0, Solid Objects follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## The promise
7
+
8
+ The public API is the set of exports, commands, and configuration options that
9
+ `README.md` and the files in `docs/` describe. [Public API](api.md) lists each
10
+ export.
11
+
12
+ | Release | What it can change |
13
+ | ------------------------ | ------------------------------------------------------------- |
14
+ | Patch, for example 1.0.1 | Corrections of defects. No new behavior |
15
+ | Minor, for example 1.1.0 | New behavior. Code that uses the public API continues to work |
16
+ | Major, for example 2.0.0 | A change that breaks code that uses the public API |
17
+
18
+ ## Supported versions
19
+
20
+ A minor release can raise the minimum version of Node.js, TypeScript, SQLite,
21
+ PostgreSQL, MySQL, or Redis. It does this only for a version that its own
22
+ project no longer supports, and [CHANGELOG.md](../CHANGELOG.md) names the
23
+ change. A patch release does not raise a minimum version.
24
+ [Supported versions](support.md) lists the current minimum versions.
25
+
26
+ ## Outside the promise
27
+
28
+ - The Cloudflare backend, `solid-objects/cloudflare`. It is experimental, and a
29
+ minor release can change it.
30
+ - Exports that the documentation does not describe.
31
+ - The database schema. A minor release can add tables, columns, and indexes.
32
+ `runtime.install()` applies each change.
33
+ - The text of error messages and log lines. The error classes are part of the
34
+ public API.
35
+ - The timing of background work, where no configuration option sets it.
36
+ - A correction of behavior that is different from the
37
+ [correctness contract](correctness.md). A patch release can make such a
38
+ correction.
39
+
40
+ ## What version 1.0 does not claim
41
+
42
+ Version 1.0 is a promise about the API. It is not a claim of measured scale.
43
+ The [benchmarks](benchmarks.md) are development measurements and do not
44
+ guarantee capacity. Longer soak tests are open work. The project has one
45
+ maintainer, and no production use by other people is known.
46
+
47
+ ## Releases before 1.0
48
+
49
+ The 0.x releases changed the API between minor versions.
50
+ [CHANGELOG.md](../CHANGELOG.md) lists each change.
@@ -7,11 +7,13 @@ Node.js. The npm package is `solid-objects`. It gives each actor a stable
7
7
  identity, durable state, ordered operations, and automatic activation.
8
8
 
9
9
  The actor runtime runs in your Node.js processes. State and mailboxes live in
10
- SQLite, PostgreSQL, or MySQL. It needs no broker, no daemon, no Cloudflare
11
- account, and no new datastore. Redis is optional and only shortens wake-up
12
- latency.
10
+ SQLite, PostgreSQL, or MySQL. It needs no separate actor service, no broker,
11
+ no Cloudflare account, and no new datastore. A process of your application
12
+ calls `runtime.run(signal)` to run reminders, effects, and queued messages.
13
+ Redis is optional and only shortens wake-up latency.
13
14
 
14
- Solid Objects is a pre-1.0 release. Read
15
+ Solid Objects 1.0 promises a stable public API. It makes no claim of measured
16
+ scale. Read
15
17
  [Compatibility and maturity](#compatibility-and-maturity) before you choose it.
16
18
 
17
19
  ## What a virtual actor is
@@ -213,6 +215,10 @@ run again without harm.
213
215
  after a crash or a lost lease.
214
216
  - Ordered turns do not cancel stale JavaScript. Fencing stops a stale
215
217
  activation from a commit, but that code can continue to run.
218
+ - An interrupted first attempt recovers immediately. Repeated interruptions
219
+ use growing, durable retry delays while keeping mailbox order. Lease loss
220
+ alone has no attempt limit; use instrumentation and an application stopping
221
+ policy when a hard bound is required.
216
222
  - External effects can run more than once. Use the stable effect ID, or
217
223
  another durable key, as the idempotency key at the provider.
218
224
  - There are no transactions across actor identities, and there is no replay of
@@ -240,7 +246,10 @@ These runtimes have different compatibility statements:
240
246
  | Browser runtime (`solid-objects/browser/host`) | The actor runtime in a browser module worker on SQLite WASM. Tested in Chromium |
241
247
  | Cloudflare backend (`solid-objects/cloudflare`) | Experimental. It runs the actor API on Cloudflare Durable Objects, with different capability limits |
242
248
 
243
- The package is pre-1.0. It has one deployed first-party reference
244
- application, no measured scale, and no known third-party production use.
249
+ Version 1.0: the public API follows [semantic versioning](versioning.md). The
250
+ Cloudflare backend is outside that promise. One first-party application with
251
+ more than 100,000 users runs the package in production. That is a count of
252
+ users, not a measure of load. The project makes no claim of measured scale,
253
+ and no production use by other people is known.
245
254
  [Supported versions](support.md) lists the CI matrix. For Ruby on Rails, use
246
255
  the [solid_objects](https://github.com/cardmagic/solid-objects-ruby) gem.
@@ -72,7 +72,7 @@ async function proveSerialization(): Promise<SerializationProof & { finalState:
72
72
  ])
73
73
  const workers = [spawnWorker(), spawnWorker()]
74
74
  await Promise.all(workers.map(({ finished }) => finished))
75
- await Promise.all(messages.map((message) => message.result()))
75
+ for (const message of messages) assert.equal(await message.status(), "completed")
76
76
  const events = await readJsonLines(
77
77
  join(controlDirectory, "serialization.jsonl"),
78
78
  parseSerializationEvent,
@@ -99,7 +99,7 @@ async function proveCrashRecovery(): Promise<{
99
99
  await wait(350)
100
100
  const recoveryWorker = spawnWorker()
101
101
  await recoveryWorker.finished
102
- await message.result()
102
+ assert.equal(await message.status(), "completed")
103
103
  return recoveryResult({ reference, message, controlDirectory })
104
104
  }
105
105
 
@@ -118,7 +118,7 @@ async function proveFencing(): Promise<{
118
118
  await wait(350)
119
119
  const recoveryWorker = spawnWorker()
120
120
  await recoveryWorker.finished
121
- await message.result()
121
+ assert.equal(await message.status(), "completed")
122
122
  staleWorker.child.kill("SIGCONT")
123
123
  await writeFile(join(controlDirectory, "release-first-attempt"), "")
124
124
  await staleWorker.waitFor((entry) => entry.event === "solid_objects.activation.lost")
@@ -138,9 +138,10 @@ async function recoveryResult(options: {
138
138
  join(options.controlDirectory, "external-effects.jsonl"),
139
139
  parseExternalEffectEvent,
140
140
  )
141
- assert.equal(attempts, 2)
141
+ assert.ok(attempts >= 2)
142
142
  assert.equal(snapshot.count, 1)
143
- assert.equal(effects.length, 2)
143
+ assert.ok(effects.length >= 2)
144
+ assert.ok(effects.length <= attempts)
144
145
  return { attempts, finalState: snapshot.count, repeatedEffects: effects.length }
145
146
  }
146
147
 
@@ -0,0 +1,10 @@
1
+ export async function processMessages(worker: {
2
+ runUntilIdle(): Promise<number>
3
+ }): Promise<number> {
4
+ let processed = 0
5
+ for (let attempt = 0; attempt < 200 && processed === 0; attempt += 1) {
6
+ processed = await worker.runUntilIdle()
7
+ if (processed === 0) await new Promise((resolve) => setTimeout(resolve, 10))
8
+ }
9
+ return processed
10
+ }
@@ -1,6 +1,7 @@
1
1
  import { createRuntime } from "solid-objects"
2
2
  import { sqlite } from "solid-objects/database/sqlite"
3
3
  import { RecoverableReport, RecoveryCounter } from "./actor.ts"
4
+ import { processMessages } from "./process-messages.ts"
4
5
 
5
6
  const databasePath = requiredArgument(2)
6
7
  const runtime = createRuntime({
@@ -36,11 +37,7 @@ if (process.argv[3] === "retire-effects") {
36
37
  const worker = runtime.worker()
37
38
 
38
39
  try {
39
- let processed = 0
40
- for (let attempt = 0; attempt < 200 && processed === 0; attempt += 1) {
41
- processed = await worker.runOnce({ activationRetention: "release" })
42
- if (processed === 0) await new Promise((resolve) => setTimeout(resolve, 10))
43
- }
40
+ const processed = await processMessages(worker)
44
41
  process.send?.({ event: "worker.finished", processed })
45
42
  } finally {
46
43
  await worker.stop()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "solid-objects",
3
- "version": "0.17.7",
4
- "description": "SQL-backed virtual actor library for TypeScript and Node.js, with durable state, ordered operations, and automatic activation on SQLite, PostgreSQL, or MySQL",
3
+ "version": "1.0.0",
4
+ "description": "Fix race conditions across Node.js processes: ordered operations, expiring holds, and state that survives a restart, in your SQL database. A SQL-backed virtual actor library for TypeScript and Node.js.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Lucas Carlson",
@@ -19,7 +19,10 @@
19
19
  "actors",
20
20
  "durable-objects",
21
21
  "virtual-actors",
22
- "actor-model"
22
+ "actor-model",
23
+ "race-conditions",
24
+ "reservations",
25
+ "transactional-outbox"
23
26
  ],
24
27
  "repository": {
25
28
  "type": "git",
@@ -118,8 +121,8 @@
118
121
  "test": "vitest run",
119
122
  "test:browser": "pnpm run build && playwright test",
120
123
  "test:coverage": "vitest run --coverage",
121
- "test:postgresql": "vitest run test/postgresql.test.ts test/activation-lease.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts test/transmit-ordering.test.ts",
122
- "test:mysql": "vitest run test/mysql.test.ts test/activation-lease.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts test/transmit-ordering.test.ts",
124
+ "test:postgresql": "vitest run test/postgresql.test.ts test/activation-lease.test.ts test/activation-recovery-backoff.test.ts test/caller-lifecycle.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts test/transmit-ordering.test.ts",
125
+ "test:mysql": "vitest run test/mysql.test.ts test/activation-lease.test.ts test/activation-recovery-backoff.test.ts test/caller-lifecycle.test.ts test/effect-recovery.test.ts test/instance-retention.test.ts test/transmit-ordering.test.ts",
123
126
  "test:package": "node scripts/release-artifact-smoke.mjs",
124
127
  "test:recovery": "pnpm run build && node examples/failure-recovery/demo.ts",
125
128
  "test:at-least-once": "pnpm run build && node examples/at-least-once/demo.ts",