Repository navigation
docs: add problem guides for races, rooms, and offline state - #70
Conversation
Developers search for a problem, not for "virtual actors". Three guides start from a failure they can reproduce, show the simplest fix without an actor, and then show a tested Solid Objects version with its limits: - Prevent race conditions in Node.js: a lost update across an await, a mutex in each process that still oversells, the conditional UPDATE fix, and an EventSeats actor whose holds, payment confirmations, and expiry reminders race. - Keep turn-based room state through restarts: process memory loses the room and its timer, a version column fixes the write, and a TurnRoom actor keeps turn order, a durable timer, and authorized reconnect. - Offline-first state in the browser with SQLite WASM: a page-memory outbox loses edits on reload, and a module worker queues writes with transmit that the server applies once, in order. Every factual claim was checked against the source and the cited primary sources. Every code block is an example file that a test runs; the race-conditions test holds seats from two real processes. The documentation check now requires each guide to embed its current example files and rejects an em dash in a guide. Vitest maps solid-objects to src so examples that import the package name run before a build; the guide tests type-check under tsconfig.examples.json, which already maps it.
|
- EventSeats rejects a capacity that is not a whole, non-negative number with invalid_capacity, and hold stops at available <= 0. A capacity of 1.5 let holds drive availability below zero. - The sync route answers 422 for UnknownOperation, so a device stops retrying a queued write for an operation that a server deploy removed. - The SQL room store uses a RoomState type and guards the stored JSON; loadRoom throws for an unexpected shape instead of returning it. - The cross-process race test kills its child process after each test, so a failed run leaves no waiting process. Each fix started from a failing test. The guides embed the new example files and describe the new behavior.
|
@greptileai Please review the latest commit, 4268a54. It addresses each finding, each one test first:
The guides embed the updated example files and describe the new behavior. Validation: format, check, |
|
|
||
| Delivery is at least once. Write each operation so that it can run again. One room runs one write at a time. This fits turn-based play, not a high-frequency game loop or fast real-time physics. | ||
|
|
||
| The application owns the WebSocket server, authentication, and the display of the room. There are no transactions across two rooms. The package requires Node.js 24.4 or newer and ESM only. The package is pre-1.0. |
There was a problem hiding this comment.
Grammar and spelling: 'and ESM only' lacks a verb; write 'and is ESM only'.
|
Docs7 for cardmagic/solid-objects-js
Commit |
A device can queue a write for an actor type that a later server deploy removes. receiveTransmitEnvelope throws UnknownActorType at enqueue, and the route rethrew it as a 500, so the device retried a write that can never apply. The route now answers 422 for UnknownActorType as it does for UnknownOperation. A new route test failed with the 500 first.
|
@greptileai Please review the latest commit, c6ca93a. Old writes keep retrying: |
Docker Hub's unauthenticated pull limit failed every database and Redis job on this branch three times in a row. The same official images are mirrored at public.ecr.aws/docker/library, which has no such limit for GitHub runners. The tag run publishes to npm only after these jobs pass, so a rate-limited run would also block a release.
|
@greptileai Please review the latest commit, 0eefeca. It only switches the CI service images (postgres, mysql, redis) to the same official images on the ECR Public mirror, because Docker Hub's unauthenticated pull limit failed the database and Redis jobs three times in a row. |
Why
Developers search for a problem ("Node.js race condition concurrent requests", "persistent Socket.IO rooms", "SQLite WASM multiple tabs"), not for "virtual actors". These guides meet that search. Each one reproduces a failure, shows the simplest adequate fix without an actor, and only then shows a tested Solid Objects version with its limits. The site will publish them at
/js/race-conditions,/js/persistent-rooms, and/browser/offline-first.Guides
docs/guides/race-conditions.mdawaittakes the last seat twice; a mutex in each process still oversellsUPDATEin aBEGIN IMMEDIATEtransactionEventSeats: holds, payment confirmation, and keyed expiry reminders on one event identity; stale-form revision check; a policy by roledocs/guides/persistent-rooms.mdMapandsetTimeoutlose the room and its timer on restartTurnRoom: ordered moves,stale_turnfor a second tab, a durable turn timer, authorized reconnect by revisiondocs/guides/offline-first.mdsharedSqliteWasmwithtransmit; a server route that authenticates and applies each write onceCompetitor and platform facts carry primary sources checked October 9, 2026: SQLite
BEGIN IMMEDIATE, Socket.IO connection state recovery (including "will not always be successful" and the adapter table), MDN Background Synchronization ("Limited availability") and Web Locks, Yjs, and Automerge.Tests
test/guide-race-conditions.test.ts(10): the race and the per-process mutex race both reproduce; the atomic fix holds across two connections; two real processes hold seats at the same time and only the capacity is held (the second runtime runs in a child process, because two synchronous SQLite connections in one event loop block each other); a repeated hold applies once; a hold expires after a restart; a late expiry after confirmation changes nothing; a confirmation after expiry rejects; a stale form rejects; a buyer cannot change capacity.test/guide-persistent-rooms.test.ts(10): memory loss on restart, the version-column fix and sweep, one move per turn across duplicate submissions, wrong player, a move for another player, a timer that skips a turn after restart, a timer that does not fire after the turn ended, no caller can run the timer, reconnect returns the current room to a member and nothing to a stranger.test/guide-offline-first.test.ts(4): the sync route refuses an unauthorized device, applies a write, applies a repeated write once, and answers 422 for a write that can never apply.test/browser/offline-first.browser.ts(3, Chromium): two tabs record three findings offline and the server applies them once, in order, after the network returns; local state survives a reload; the page-memory outbox loses its edits on reload.Supporting changes
scripts/check-documentation.mjslink-checksdocs/guides/, requires each guide to embed its current example files, and rejects an em dash in a guide.vitest.config.tsmapssolid-objects,solid-objects/core, andsolid-objects/database/sqlitetosrc/, so examples that import the package name run under test before a build. The guide tests type-check undertsconfig.examples.json, which already maps them;tsconfig.jsonexcludes them so the build configs stay unchanged.test/support/source-hooks.mjslets the child process run the example TypeScript fromsrc/.test/browser-server.mjsserves the guide example and adds a test-only sync route with a network switch.docs/agents.md.Edits to the drafted copy
race-conditions.md: "Theavailablefield" changed to "Theavailablegetter" (it is a getter, which the runtime treats as a query).Validation
pnpm run format:check,pnpm run check,pnpm test(613 passed, 32 database skips),pnpm run test:coverage,pnpm run build,pnpm run pack:check(the tarball contains the 3 guides and 15 example files),pnpm run test:package,pnpm run test:recovery,pnpm run test:at-least-once,pnpm run test:browser(16 passed),pnpm audit --audit-level=high(no known vulnerabilities). The race-conditions, rooms, and offline-first tests also passed in repeated runs.