-
Notifications
You must be signed in to change notification settings - Fork 1
Lower the Node floor to 24.4.0 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3a6e397
9442c7a
9be5c93
c9b7d51
cad1924
fd29399
59e4a4f
7824c0b
b201fe2
87d91df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,11 +60,12 @@ processes submit them concurrently. | |
|
|
||
| ## Run it now with SQLite | ||
|
|
||
| Node.js 24.15 or newer is required. The `0.13.2` release includes a | ||
| packaged quickstart: | ||
| Node.js 24.4.0 or newer is required. Node.js 24.15 or newer is preferred, | ||
| because `node:sqlite` prints an experimental warning before it. The `0.13.3` | ||
| release includes a packaged quickstart: | ||
|
Comment on lines
+63
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a user on Node 24.4.0–24.14.x runs the documented quickstart with npm engine-strict enabled, the command installs Prompt To Fix With AIThis is a comment left during a code review.
Path: README.md
Line: 60-62
Comment:
**Pinned quickstart requires newer Node**
When a user on Node 24.4.0–24.14.x runs the documented quickstart with npm engine-strict enabled, the command installs `solid-objects@0.13.1`, whose engine constraint remains `>=24.15.0`, causing npm to reject the installation despite the new stated minimum.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
||
|
|
||
| ```bash | ||
| npm exec --yes --package=solid-objects@0.13.2 -- solid-objects quickstart | ||
| npm exec --yes --package=solid-objects@0.13.3 -- solid-objects quickstart | ||
| ``` | ||
|
|
||
| The command needs no repository checkout, database server, Redis, container, or | ||
|
|
@@ -258,7 +259,8 @@ edge placement, cross-identity transactions, and operational data access—is in | |
|
|
||
| ## Requirements and supported systems | ||
|
|
||
| - Node.js 24.15 or newer | ||
| - Node.js 24.4.0 or newer; 24.15 or newer to avoid the `node:sqlite` | ||
| experimental warning | ||
| - TypeScript 5.9 or newer for TypeScript applications | ||
| - SQLite through `node:sqlite`, PostgreSQL 14 or newer, or MySQL 8.0 or newer | ||
| with InnoDB | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| import assert from "node:assert/strict" | ||
|
|
||
| export interface SerializationEvent { | ||
| event: "start" | "finish" | ||
| messageId: string | ||
| attempt: number | ||
| processId: number | ||
| at: number | ||
| } | ||
|
|
||
| export interface SerializationProof { | ||
| executions: number | ||
| retried: boolean | ||
| supersededOverlap: boolean | ||
| } | ||
|
|
||
| interface Execution { | ||
| messageId: string | ||
| attempt: number | ||
| processId: number | ||
| startedAt: number | ||
| finishedAt: number | ||
| } | ||
|
|
||
| export function parseSerializationEvent(line: string): SerializationEvent { | ||
| const event = JSON.parse(line) as Partial<SerializationEvent> | ||
| if ( | ||
| (event.event !== "start" && event.event !== "finish") || | ||
| typeof event.messageId !== "string" || | ||
| typeof event.attempt !== "number" || | ||
| typeof event.processId !== "number" || | ||
| typeof event.at !== "number" | ||
| ) { | ||
| throw new TypeError("invalid serialization event") | ||
| } | ||
| return { | ||
| event: event.event, | ||
| messageId: event.messageId, | ||
| attempt: event.attempt, | ||
| processId: event.processId, | ||
| at: event.at, | ||
| } | ||
| } | ||
|
|
||
| // One identity commits one state transition at a time. The control file is | ||
| // written outside the transaction, so it records execution attempts rather than | ||
| // commits: a worker that loses its lease keeps running until it notices, and its | ||
| // replacement executes the same message under a higher attempt. The superseded | ||
| // attempt may therefore overlap anything, because its write is fenced out and | ||
| // the committed state is what proves it. | ||
| // | ||
| // Each event carries its attempt and process, so a start pairs with its own | ||
| // finish rather than with whichever finish arrived next. Without that, a | ||
| // superseded attempt finishing late reads as its replacement finishing, and a | ||
| // second message could then overlap a replacement that is still running. | ||
| export function assertSerializedExecution( | ||
| events: readonly SerializationEvent[], | ||
| options: { messageCount: number }, | ||
| ): SerializationProof { | ||
| const executions = pairExecutions(events) | ||
|
|
||
| const messageIds = new Set(executions.map((execution) => execution.messageId)) | ||
| assert.equal( | ||
| messageIds.size, | ||
| options.messageCount, | ||
| `expected ${options.messageCount} messages to run, saw ${messageIds.size}`, | ||
| ) | ||
|
|
||
| const survivingAttempt = new Map<string, number>() | ||
| for (const execution of executions) { | ||
| const highest = survivingAttempt.get(execution.messageId) ?? 0 | ||
| if (execution.attempt > highest) survivingAttempt.set(execution.messageId, execution.attempt) | ||
| } | ||
| const surviving = executions.filter( | ||
| (execution) => survivingAttempt.get(execution.messageId) === execution.attempt, | ||
| ) | ||
|
|
||
| for (const [index, execution] of surviving.entries()) { | ||
| for (const other of surviving.slice(index + 1)) { | ||
| assert( | ||
| !overlaps(execution, other), | ||
| `${describe(execution)} and ${describe(other)} overlap, and neither was superseded`, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| const supersededOverlap = executions.some((execution) => | ||
| executions.some((other) => other !== execution && overlaps(execution, other)), | ||
| ) | ||
|
|
||
| return { | ||
| executions: executions.length, | ||
| retried: executions.length > options.messageCount, | ||
| supersededOverlap, | ||
| } | ||
| } | ||
|
|
||
| function pairExecutions(events: readonly SerializationEvent[]): Execution[] { | ||
| const started = new Map<string, SerializationEvent>() | ||
| const executions: Execution[] = [] | ||
|
|
||
| for (const event of [...events].sort((left, right) => left.at - right.at)) { | ||
| const key = `${event.messageId}#${event.attempt}#${event.processId}` | ||
| if (event.event === "start") { | ||
| assert(!started.has(key), `${describe(event)} started twice`) | ||
| started.set(key, event) | ||
| continue | ||
| } | ||
| const start = started.get(key) | ||
| assert(start !== undefined, `${describe(event)} finished with no matching start`) | ||
| started.delete(key) | ||
| executions.push({ | ||
| messageId: event.messageId, | ||
| attempt: event.attempt, | ||
| processId: event.processId, | ||
| startedAt: start.at, | ||
| finishedAt: event.at, | ||
| }) | ||
| } | ||
|
|
||
| const unfinished = [...started.values()].map(describe) | ||
| assert.equal(unfinished.length, 0, `${unfinished.join(", ")} never wrote a finish`) | ||
|
|
||
| return executions | ||
| } | ||
|
|
||
| function overlaps(left: Execution, right: Execution): boolean { | ||
| return left.startedAt < right.finishedAt && right.startedAt < left.finishedAt | ||
| } | ||
|
|
||
| function describe(execution: { messageId: string; attempt: number }): string { | ||
| return `${execution.messageId} attempt ${execution.attempt}` | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The floor job does not run
test:package, so it does not continuously validate clean installation and execution of the generated package at Node 24.4.0; a floor-specific packaging or executable incompatibility can therefore pass the publish gate.Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!