solid-objects 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/README.md +185 -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/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/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 +199 -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 +3 -0
- package/docs/operations.md +33 -19
- package/docs/parity.md +125 -46
- package/docs/releasing.md +6 -6
- package/docs/state-and-lifecycle.md +12 -8
- package/docs/support.md +45 -20
- package/examples/quickstart-report.ts +151 -0
- package/examples/sqlite-quickstart.ts +76 -16
- package/package.json +28 -2
package/docs/parity.md
CHANGED
|
@@ -8,13 +8,24 @@ Reference: Ruby `solid_objects` 0.13.3. The JavaScript package began at the
|
|
|
8
8
|
Ruby design's `0.12` capability generation; that version number did not imply
|
|
9
9
|
earlier JavaScript releases.
|
|
10
10
|
|
|
11
|
-
The Node `0.
|
|
11
|
+
The Node `0.14.0` implementation has capability parity with that reference. Its
|
|
12
12
|
relational runtime, correctness boundaries, administration, diagnostics,
|
|
13
13
|
operator dashboard, realtime projections, browser behavior, and supported
|
|
14
|
-
adapters have native equivalents.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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.
|
|
19
|
+
|
|
20
|
+
`0.14.0` also adds `runtime.enqueueInternalMessage()`,
|
|
21
|
+
`runtime.enqueueInternalMessageInTransaction()`, and
|
|
22
|
+
`runtime.snapshotWithIncarnation()`. These are Node-only integration points
|
|
23
|
+
for a host package (such as a future commercial scaling layer), not ported
|
|
24
|
+
Ruby capabilities. Ruby's equivalent primitives
|
|
25
|
+
(`SolidObjects::Mailbox#enqueue`, `ActorSnapshot`) are already reachable
|
|
26
|
+
in-process, and they need no dedicated public API. Node's `exports` map enforces
|
|
27
|
+
a package-privacy boundary between a package and its dependents. Ruby has no
|
|
28
|
+
such boundary between a gem and its dependents.
|
|
18
29
|
|
|
19
30
|
## Status vocabulary
|
|
20
31
|
|
|
@@ -29,53 +40,60 @@ boundaries, not missing Ruby capabilities.
|
|
|
29
40
|
|
|
30
41
|
## Runtime and correctness
|
|
31
42
|
|
|
32
|
-
| Capability | Status | TypeScript shape or remaining work
|
|
33
|
-
| ------------------------------------------------------------------------------------------------------------- | ------- |
|
|
34
|
-
| 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.
|
|
35
|
-
| Fluent committed calls and background delivery | Native | `await reference.operation()` and `reference.send.operation()`.
|
|
36
|
-
| 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, and adapter-appropriate sequence locking.
|
|
37
|
-
| 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.
|
|
38
|
-
| 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.
|
|
39
|
-
| Bounded claim candidate scan | Native | A configurable ordered scan continues to another ready actor when a worker loses the first candidate's lease race.
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
| Result
|
|
43
|
+
| Capability | Status | TypeScript shape or remaining work |
|
|
44
|
+
| ------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| 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. |
|
|
46
|
+
| Fluent committed calls and background delivery | Native | `await reference.operation()` and `reference.send.operation()`. |
|
|
47
|
+
| 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, and adapter-appropriate sequence locking. |
|
|
48
|
+
| 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. |
|
|
49
|
+
| 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. |
|
|
50
|
+
| Bounded claim candidate scan | Native | A configurable ordered scan continues to another ready actor when a worker loses the first candidate's lease race. |
|
|
51
|
+
| 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 and global admission control do not exist yet, matching the open Ruby roadmap item. |
|
|
52
|
+
| 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. |
|
|
53
|
+
| 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. |
|
|
54
|
+
| Actor-to-actor delivery | Native | `sendTo(reference).operation()` stages delivery in the source actor commit. |
|
|
55
|
+
| One-shot and recurring reminders | Native | Scheduling, replacement events, catch-up policy, stale-claim recovery, pausing, authorized inspection, and idempotent resume are implemented. |
|
|
56
|
+
| Same-database commit actions | Native | Registered actions receive source-message identity, mailbox sequence, activation generation, and the fenced transaction connection. |
|
|
57
|
+
| 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. |
|
|
58
|
+
| 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. |
|
|
59
|
+
| 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. |
|
|
60
|
+
| 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. |
|
|
61
|
+
| 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. |
|
|
62
|
+
| Result lookup by request ID | Planned | This is also an open Ruby roadmap item and will be implemented in both runtimes when its authorization shape is settled. |
|
|
51
63
|
|
|
52
64
|
## Operations
|
|
53
65
|
|
|
54
|
-
| Capability | Status | TypeScript shape or remaining work
|
|
55
|
-
| ----------------------------------------------------------------------------- | ------ |
|
|
56
|
-
| 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.
|
|
57
|
-
| Failed-role replacement | Native | Built-in and registered roles are rebuilt through their factories with capped backoff; shutdown is the terminal replacement boundary.
|
|
58
|
-
| Additional supervised components | Native | `registerComponent()` builds, validates, runs, and stops application components with the runtime.
|
|
59
|
-
| Dead-letter inspection and retry | Native | `runtime.deadLetters` provides deny-by-default immutable inspection and idempotent durable retry linkage.
|
|
60
|
-
| Reconciliation reads | Native | Authorized cursor pages cover active, quiet, and orphaned instances; bounded state batches are migrated and deeply frozen.
|
|
61
|
-
| 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.
|
|
62
|
-
| 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.
|
|
63
|
-
| CLI | Native | The packaged executable loads an application runtime and exposes start, diagnostics, processes, dead letters, reminders, and explicit retention pruning as JSON.
|
|
64
|
-
| 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. |
|
|
65
|
-
| Structured instrumentation | Native | An isolated transport-neutral sink emits immutable lifecycle metadata and structurally excludes application payloads.
|
|
66
|
-
| Public test helper | Native | `runtime.testing` provides role-selective deterministic draining, explicit-time due-reminder execution, and dependency-ordered reset without relying on cascades.
|
|
66
|
+
| Capability | Status | TypeScript shape or remaining work |
|
|
67
|
+
| ----------------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| 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. |
|
|
69
|
+
| Failed-role replacement | Native | Built-in and registered roles are rebuilt through their factories with capped backoff; shutdown is the terminal replacement boundary. |
|
|
70
|
+
| Additional supervised components | Native | `registerComponent()` builds, validates, runs, and stops application components with the runtime. |
|
|
71
|
+
| Dead-letter inspection and retry | Native | `runtime.deadLetters` provides deny-by-default immutable inspection and idempotent durable retry linkage. |
|
|
72
|
+
| Reconciliation reads | Native | Authorized cursor pages cover active, quiet, and orphaned instances; bounded state batches are migrated and deeply frozen. |
|
|
73
|
+
| 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. |
|
|
74
|
+
| 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. |
|
|
75
|
+
| CLI | Native | The packaged executable loads an application runtime and exposes start, diagnostics, processes, dead letters, reminders, and explicit retention pruning as JSON. |
|
|
76
|
+
| 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. |
|
|
77
|
+
| Structured instrumentation | Native | An isolated transport-neutral sink emits immutable lifecycle metadata and structurally excludes application payloads. |
|
|
78
|
+
| Public test helper | Native | `runtime.testing` provides role-selective deterministic draining, explicit-time due-reminder execution, and dependency-ordered reset without relying on cascades. |
|
|
67
79
|
|
|
68
80
|
## Databases and wake-up
|
|
69
81
|
|
|
70
|
-
| Capability | Status | TypeScript shape or remaining work
|
|
71
|
-
| ------------------------ | ------ |
|
|
72
|
-
| 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.
|
|
73
|
-
| 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.
|
|
74
|
-
| 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.
|
|
75
|
-
| Durable polling fallback | Native | Every role progresses without a notification service.
|
|
76
|
-
| 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.
|
|
77
|
-
| PostgreSQL wake-up | Native | `database.wakeUp()` uses one dedicated event-driven client, role-specific `LISTEN/NOTIFY`, generation fencing, reconnectable listeners, and durable polling fallback.
|
|
78
|
-
| 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.
|
|
82
|
+
| Capability | Status | TypeScript shape or remaining work |
|
|
83
|
+
| ------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| 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. |
|
|
85
|
+
| 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. |
|
|
86
|
+
| 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. |
|
|
87
|
+
| Durable polling fallback | Native | Every role progresses without a notification service. |
|
|
88
|
+
| 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. |
|
|
89
|
+
| PostgreSQL wake-up | Native | `database.wakeUp()` uses one dedicated event-driven client, role-specific `LISTEN/NOTIFY`, generation fencing, reconnectable listeners, and durable polling fallback. |
|
|
90
|
+
| 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. |
|
|
91
|
+
|
|
92
|
+
Every wake-up adapter above is opt-in. Neither runtime selects one
|
|
93
|
+
automatically. An application that configures nothing keeps polling. Each
|
|
94
|
+
runtime warns once when live processes share a database without a configured
|
|
95
|
+
cross-process adapter. This limit is intentional in both runtimes. It is not a
|
|
96
|
+
gap between them.
|
|
79
97
|
|
|
80
98
|
## Realtime and browser behavior
|
|
81
99
|
|
|
@@ -89,6 +107,67 @@ boundaries, not missing Ruby capabilities.
|
|
|
89
107
|
| 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. |
|
|
90
108
|
| Real-browser compatibility suite | Native | Playwright exercises subscription replay over native WebSocket, incarnation/revision fences, payload delivery, component batching, and cancellation in Chromium. |
|
|
91
109
|
|
|
110
|
+
## JavaScript-only: the browser runtime
|
|
111
|
+
|
|
112
|
+
`0.14.0` ships an in-browser runtime with SQLite WASM storage
|
|
113
|
+
([#17](https://github.com/cardmagic/solid-objects-js/issues/17)). The
|
|
114
|
+
runtime itself is a JavaScript-only capability: the Ruby gem has no browser
|
|
115
|
+
target, so no Ruby parity row exists for milestones M1 through M3. The
|
|
116
|
+
transmit family (milestone M4) started here but is not JavaScript-only; the
|
|
117
|
+
next section tracks it as a shared capability. All four milestones are
|
|
118
|
+
complete:
|
|
119
|
+
|
|
120
|
+
- M1: the shared modules no longer import Node built-in modules, a
|
|
121
|
+
registered platform factory supplies async context propagation, and
|
|
122
|
+
`pnpm run check` enforces a Node-free import graph for the browser-safe
|
|
123
|
+
modules.
|
|
124
|
+
- M2: `solid-objects/database/sqlite-wasm` implements the `Database`
|
|
125
|
+
contract on SQLite WASM. The full runtime passes a round-trip test
|
|
126
|
+
against it, and Playwright proves OPFS persistence across a page reload.
|
|
127
|
+
- M3: `solid-objects/browser/host` hosts the full runtime in a browser
|
|
128
|
+
module worker on OPFS storage, with durable actor state across page
|
|
129
|
+
reloads proven in Chromium. `solid-objects/browser/tab-host` elects one
|
|
130
|
+
leader per origin with the Web Locks API and serves every tab over a
|
|
131
|
+
`BroadcastChannel`; Playwright proves shared state across two tabs and
|
|
132
|
+
failover with durable continuation after the leader tab closes.
|
|
133
|
+
`solid-objects/database/shared-sqlite-wasm` goes further: it moves the
|
|
134
|
+
election behind the `Database` seam, so every tab runs the ordinary
|
|
135
|
+
`configure -> install -> ref` flow and the runtime's own leases and
|
|
136
|
+
fencing arbitrate the tabs' workers. The plan
|
|
137
|
+
named a `SharedWorker` as the host; Web Locks election between dedicated
|
|
138
|
+
workers replaced it, because OPFS sync access handles exist only in
|
|
139
|
+
dedicated workers.
|
|
140
|
+
- M4: `solid-objects/transmit` drains the local effects outbox to a
|
|
141
|
+
server runtime with at-least-once delivery, per-actor order, and an
|
|
142
|
+
idempotent server ingest. Vitest proves order under transmit failures,
|
|
143
|
+
replay deduplication, and recovery after an offline period, on SQLite,
|
|
144
|
+
PostgreSQL, and MySQL.
|
|
145
|
+
|
|
146
|
+
## Shared capability: the transmit family
|
|
147
|
+
|
|
148
|
+
The transmit family is the one part of the browser work that both runtimes
|
|
149
|
+
share. The Ruby gem adopts it in
|
|
150
|
+
[solid-objects-ruby#49](https://github.com/cardmagic/solid-objects-ruby/pull/49)
|
|
151
|
+
(proposals [#47](https://github.com/cardmagic/solid-objects-ruby/issues/47)
|
|
152
|
+
and [#48](https://github.com/cardmagic/solid-objects-ruby/issues/48)):
|
|
153
|
+
`SolidObjects::Transmission.receive` is the ingest, and `Actor#transmit`
|
|
154
|
+
with `register_transmit` is the staging side. Identifiers differ by
|
|
155
|
+
runtime idiom; the wire contract is what both sides guarantee:
|
|
156
|
+
|
|
157
|
+
- envelope keys are camelCase (`effectId`, `actorType`, `actorId`,
|
|
158
|
+
`operation`, and an optional `arguments` that defaults to an empty
|
|
159
|
+
object);
|
|
160
|
+
- the ingest idempotency key is `transmit:<effectId>`, byte for byte;
|
|
161
|
+
- a replay with changed arguments raises the idempotency conflict on both
|
|
162
|
+
sides and leaves the first application intact.
|
|
163
|
+
|
|
164
|
+
`compatibility/transmit-envelopes.json` is committed to both repositories
|
|
165
|
+
with a consuming test on each side, so the contract is enforced from both
|
|
166
|
+
sides of the repository boundary. Manual cross-runtime QA (Node to Rails
|
|
167
|
+
and Rails to Node) ran in solid-objects-ruby#49; the one disagreement it
|
|
168
|
+
found (the optional `arguments` default) is fixed and pinned by the shared
|
|
169
|
+
fixture.
|
|
170
|
+
|
|
92
171
|
## Rails-specific surfaces
|
|
93
172
|
|
|
94
173
|
Rails generators, Active Record models/controllers, Turbo rendering, and
|
package/docs/releasing.md
CHANGED
|
@@ -26,8 +26,8 @@ npm trust github solid-objects \
|
|
|
26
26
|
|
|
27
27
|
## Release procedure
|
|
28
28
|
|
|
29
|
-
1. Update the version in `package.json` and `src/version.ts
|
|
30
|
-
lockfile when
|
|
29
|
+
1. Update the version in `package.json` and `src/version.ts`. Refresh the
|
|
30
|
+
lockfile when necessary. Move the release notes out of the Unreleased
|
|
31
31
|
section in `CHANGELOG.md` into a dated section for the new version. The
|
|
32
32
|
publish job reads that section, so a version without one fails the release.
|
|
33
33
|
2. Run `pnpm run format:check`, `pnpm run check`, `pnpm run test:coverage`,
|
|
@@ -48,7 +48,7 @@ quality, database, Redis, and browser job succeeds. It rejects tags that do not
|
|
|
48
48
|
match `package.json`, safely skips versions already present in npm, and
|
|
49
49
|
publishes new versions with npm provenance.
|
|
50
50
|
|
|
51
|
-
The job then builds the release notes with `scripts/release-notes.mjs
|
|
52
|
-
prints the `CHANGELOG.md` section for the tagged version
|
|
53
|
-
release for the tag.
|
|
54
|
-
creates a missing release.
|
|
51
|
+
The job then builds the release notes with `scripts/release-notes.mjs`. That
|
|
52
|
+
script prints the `CHANGELOG.md` section for the tagged version. The job then
|
|
53
|
+
creates the GitHub release for the tag. If you run the job again on a tag that
|
|
54
|
+
npm already holds, it still creates a missing release.
|
|
@@ -17,8 +17,8 @@ fields, then walks its prototype chain to discover methods and getters.
|
|
|
17
17
|
- Operation, field, and getter names must not collide with the reference API.
|
|
18
18
|
|
|
19
19
|
The constructor must establish every persisted field and must not depend on
|
|
20
|
-
external state. Solid Objects invokes it
|
|
21
|
-
|
|
20
|
+
external state. Solid Objects invokes it at four points: class validation,
|
|
21
|
+
default creation, state hydration, and snapshot projection.
|
|
22
22
|
|
|
23
23
|
## Observable broadcast modes
|
|
24
24
|
|
|
@@ -79,12 +79,16 @@ asynchronous work or write through a database wrapped by
|
|
|
79
79
|
a failing migration, or state newer than the running code raises
|
|
80
80
|
`StateMigrationError`.
|
|
81
81
|
|
|
82
|
-
For a destructive shape change, use expand/contract deployment:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
For a destructive shape change, use an expand/contract deployment:
|
|
83
|
+
|
|
84
|
+
1. deploy readers that understand both shapes;
|
|
85
|
+
2. deploy the migration;
|
|
86
|
+
3. wait for operational evidence that the actors advanced;
|
|
87
|
+
4. remove the old-shape support.
|
|
88
|
+
|
|
89
|
+
If old code cannot understand the new shape, drain it before new code persists
|
|
90
|
+
the migration. Keep the old migration steps. An actor that stays idle for
|
|
91
|
+
several releases can then still advance one version at a time.
|
|
88
92
|
|
|
89
93
|
Actor state migration is separate from `runtime.install()`. The latter applies
|
|
90
94
|
the package's relational schema migrations; it does not eagerly rewrite actor
|
package/docs/support.md
CHANGED
|
@@ -2,18 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## Runtime support
|
|
4
4
|
|
|
5
|
-
| Component
|
|
6
|
-
|
|
|
7
|
-
| Node.js
|
|
8
|
-
| TypeScript
|
|
9
|
-
| SQLite
|
|
10
|
-
| PostgreSQL
|
|
11
|
-
| MySQL
|
|
12
|
-
| Redis wake-up
|
|
13
|
-
| Browser client
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
| Component | Supported or tested range |
|
|
6
|
+
| --------------- | ----------------------------------------------------------- |
|
|
7
|
+
| Node.js | 24.4.0 or newer; CI runs 24.4.0 and 24.15.0 |
|
|
8
|
+
| TypeScript | 5.9 or newer for TypeScript applications |
|
|
9
|
+
| SQLite | Node's built-in `node:sqlite` on the supported Node runtime |
|
|
10
|
+
| PostgreSQL | 14 or newer; CI runs 14 and 18 |
|
|
11
|
+
| MySQL | 8.0 or newer with InnoDB; CI runs 8.0 and 8.4 |
|
|
12
|
+
| Redis wake-up | Optional; CI runs Redis 7 |
|
|
13
|
+
| Browser client | Chromium through Playwright |
|
|
14
|
+
| SQLite WASM | `@sqlite.org/sqlite-wasm` 3.50 or newer; optional |
|
|
15
|
+
| Browser runtime | Chromium through Playwright; OPFS for persistent storage |
|
|
16
|
+
|
|
17
|
+
The package is ESM-only. PostgreSQL, MySQL, Redis, and SQLite WASM require
|
|
18
|
+
their optional peer dependency. The Node SQLite adapter has no driver
|
|
19
|
+
dependency beyond Node.js.
|
|
20
|
+
|
|
21
|
+
The browser runtime needs two platform capabilities:
|
|
22
|
+
|
|
23
|
+
- Persistent storage uses the OPFS SAH pool VFS, which needs a secure context
|
|
24
|
+
and a dedicated worker. `sqliteWasm({ storage: "persistent" })` fails fast
|
|
25
|
+
where OPFS is unavailable; temporary storage works everywhere the WASM
|
|
26
|
+
module loads.
|
|
27
|
+
- The tab host election uses the Web Locks API. Every current browser
|
|
28
|
+
provides it. Node.js provides `navigator.locks` from 24.5, so Node-side use
|
|
29
|
+
of `solid-objects/browser/tab-host` needs a newer Node than the package
|
|
30
|
+
floor; the tab host test suite skips on older Node.
|
|
17
31
|
|
|
18
32
|
The Node.js floor is 24.4.0 because the SQLite adapter reads integer columns as
|
|
19
33
|
`BigInt`. Node.js 24.4.0 is the first release that accepts `readBigInts` on the
|
|
@@ -28,19 +42,30 @@ can change. Prefer 24.15.0 or newer where the choice is free.
|
|
|
28
42
|
|
|
29
43
|
## What the matrix covers
|
|
30
44
|
|
|
31
|
-
The default suite exercises
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
The default suite exercises:
|
|
46
|
+
|
|
47
|
+
- actor definitions, mailbox order, and state migrations;
|
|
48
|
+
- leases, fencing, retries, and dead letters;
|
|
49
|
+
- effects, reminders, and realtime outboxes;
|
|
50
|
+
- administration, authorization, and retention;
|
|
51
|
+
- lifecycle, timeouts, and SQLite behavior.
|
|
35
52
|
|
|
36
53
|
Database jobs run the real adapter suites against PostgreSQL and MySQL servers.
|
|
37
54
|
The Redis job runs wake-up behavior against a real Redis server. The browser
|
|
38
55
|
job uses native WebSocket connections and Chromium for replay, payload,
|
|
39
|
-
component, dashboard, and revision-fence behavior.
|
|
56
|
+
component, dashboard, and revision-fence behavior. It also runs the browser
|
|
57
|
+
runtime suites in Chromium module workers: SQLite WASM transactions and OPFS
|
|
58
|
+
persistence across page reloads, the full runtime with durable actor state,
|
|
59
|
+
deadline rollback under the turn-scoped context store, two tabs on one
|
|
60
|
+
runtime with leader failover, and the sync bridge drain into a Node runtime.
|
|
61
|
+
|
|
62
|
+
The quality job also:
|
|
40
63
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
1. builds the ESM package;
|
|
65
|
+
2. inspects `npm pack`;
|
|
66
|
+
3. installs the tarball in a clean temporary project;
|
|
67
|
+
4. runs the packaged SQLite quickstart;
|
|
68
|
+
5. executes the multi-process recovery demonstration.
|
|
44
69
|
|
|
45
70
|
## Boundaries
|
|
46
71
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export interface QuickstartSummary {
|
|
2
|
+
sameIdentityCalls: number
|
|
3
|
+
sameIdentityFinalState: number
|
|
4
|
+
independentIdentitiesOverlapped: boolean
|
|
5
|
+
temporaryStateRemoved: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface QuickstartCheck {
|
|
9
|
+
passed: boolean
|
|
10
|
+
title: string
|
|
11
|
+
detail: readonly string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const INSTALL_COMMAND = "npm install solid-objects"
|
|
15
|
+
const AGENT_PROMPT = "where would the solid-objects library be best used in this app?"
|
|
16
|
+
const DOCUMENTATION_URL = "https://solidobjects.dev/node"
|
|
17
|
+
|
|
18
|
+
export function formatQuickstartPlan(plan: {
|
|
19
|
+
sameIdentityCalls: number
|
|
20
|
+
actorSource: string
|
|
21
|
+
}): string {
|
|
22
|
+
return [
|
|
23
|
+
"Solid Objects quickstart",
|
|
24
|
+
"",
|
|
25
|
+
"This command will:",
|
|
26
|
+
"",
|
|
27
|
+
" 1. create a temporary SQLite database;",
|
|
28
|
+
` 2. send ${plan.sameIdentityCalls} concurrent calls to one identity;`,
|
|
29
|
+
" 3. run two other identities at the same time;",
|
|
30
|
+
" 4. close the runtime and delete the temporary database.",
|
|
31
|
+
"",
|
|
32
|
+
"No server, container, or configuration is needed.",
|
|
33
|
+
"",
|
|
34
|
+
"The actor it runs:",
|
|
35
|
+
"",
|
|
36
|
+
...plan.actorSource.split("\n").map((line) => ` ${line}`.trimEnd()),
|
|
37
|
+
"",
|
|
38
|
+
"",
|
|
39
|
+
].join("\n")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatQuickstartPrompt(): string {
|
|
43
|
+
return "Run it now? [Y/n] "
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function answerAllowsRun(answer: string): boolean {
|
|
47
|
+
const value = answer.trim().toLowerCase()
|
|
48
|
+
if (value === "") return true
|
|
49
|
+
return value === "y" || value === "yes"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function formatQuickstartStop(): string {
|
|
53
|
+
return ["", "Nothing ran. No database and no files were created.", ""].join("\n")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatQuickstartReport(summary: QuickstartSummary): string {
|
|
57
|
+
const results = checks(summary)
|
|
58
|
+
return [
|
|
59
|
+
"Results",
|
|
60
|
+
"",
|
|
61
|
+
...results.flatMap(checkLines),
|
|
62
|
+
"",
|
|
63
|
+
"Each line above is an assertion, not a print. The command exits with a",
|
|
64
|
+
"non-zero code when one of them fails.",
|
|
65
|
+
"",
|
|
66
|
+
...meaning(results),
|
|
67
|
+
"Add it to your app",
|
|
68
|
+
"",
|
|
69
|
+
` ${INSTALL_COMMAND}`,
|
|
70
|
+
"",
|
|
71
|
+
"Or tell your agent",
|
|
72
|
+
"",
|
|
73
|
+
` ${AGENT_PROMPT}`,
|
|
74
|
+
"",
|
|
75
|
+
`Docs: ${DOCUMENTATION_URL}`,
|
|
76
|
+
"",
|
|
77
|
+
].join("\n")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function meaning(results: readonly QuickstartCheck[]): string[] {
|
|
81
|
+
if (results.some((check) => !check.passed)) return []
|
|
82
|
+
return [
|
|
83
|
+
"What each PASS means",
|
|
84
|
+
"",
|
|
85
|
+
" Two requests on the same cart cannot overwrite each other.",
|
|
86
|
+
" That identity has one durable mailbox, so its calls commit one at a",
|
|
87
|
+
" time and no update is lost.",
|
|
88
|
+
"",
|
|
89
|
+
" Unrelated identities do not wait for that mailbox.",
|
|
90
|
+
" The order is per identity, not global.",
|
|
91
|
+
"",
|
|
92
|
+
" The state lives in an ordinary SQL database, so it survives a restart.",
|
|
93
|
+
"",
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function checkLines(check: QuickstartCheck): string[] {
|
|
98
|
+
const result = check.passed ? "PASS" : "FAIL"
|
|
99
|
+
return [`${result} ${check.title}`, ...check.detail.map((line) => ` ${line}`)]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function checks(summary: QuickstartSummary): QuickstartCheck[] {
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
passed: summary.sameIdentityFinalState === summary.sameIdentityCalls,
|
|
106
|
+
title: `${summary.sameIdentityCalls} concurrent calls to one identity`,
|
|
107
|
+
detail: sameIdentityDetail(summary),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
passed: summary.independentIdentitiesOverlapped,
|
|
111
|
+
title: "Two different identities ran at the same time",
|
|
112
|
+
detail: overlapDetail(summary),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
passed: summary.temporaryStateRemoved,
|
|
116
|
+
title: "Temporary state removed",
|
|
117
|
+
detail: cleanupDetail(summary),
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sameIdentityDetail(summary: QuickstartSummary): string[] {
|
|
123
|
+
if (summary.sameIdentityFinalState !== summary.sameIdentityCalls) {
|
|
124
|
+
return [
|
|
125
|
+
`The committed state is ${summary.sameIdentityFinalState} after ${summary.sameIdentityCalls} calls.`,
|
|
126
|
+
"The runtime lost an update.",
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
return [
|
|
130
|
+
"They ran in order on one mailbox.",
|
|
131
|
+
`The committed state is ${summary.sameIdentityFinalState}.`,
|
|
132
|
+
`The return values were the complete sequence 1 through ${summary.sameIdentityCalls}.`,
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function overlapDetail(summary: QuickstartSummary): string[] {
|
|
137
|
+
if (!summary.independentIdentitiesOverlapped) {
|
|
138
|
+
return ["Their execution windows did not overlap."]
|
|
139
|
+
}
|
|
140
|
+
return [
|
|
141
|
+
"Their execution windows overlapped, so an unrelated identity",
|
|
142
|
+
"never waits behind this one.",
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function cleanupDetail(summary: QuickstartSummary): string[] {
|
|
147
|
+
if (!summary.temporaryStateRemoved) {
|
|
148
|
+
return ["The temporary SQLite database is still on disk."]
|
|
149
|
+
}
|
|
150
|
+
return ["The scoped temporary SQLite database was deleted at exit."]
|
|
151
|
+
}
|