@douglance/stdb-eslint-plugin
v1.0.0
Published
ESLint rules for SpacetimeDB V8 runtime pitfalls - catch serialization bugs at IDE level
Downloads
10
Maintainers
Readme
@spacetimedb/eslint-plugin
ESLint plugin that catches SpacetimeDB V8 runtime serialization bugs at IDE level, preventing runtime crashes before they reach production.
Installation
npm install --save-dev @spacetimedb/eslint-pluginUsage
Add to your .eslintrc.js:
module.exports = {
plugins: ['@spacetimedb'],
extends: ['plugin:@spacetimedb/recommended'],
};Or configure rules individually:
module.exports = {
plugins: ['@spacetimedb'],
rules: {
'@spacetimedb/no-template-literals-in-reducer': 'error',
'@spacetimedb/no-foreach-in-reducer': 'error',
'@spacetimedb/no-filter-on-table-iter': 'warn',
'@spacetimedb/no-insert-id-in-lifecycle-hooks': 'error',
},
};Rules
no-template-literals-in-reducer (error)
Problem: Template literals cause TypeError: r.charCodeAt is not a function due to V8 serialization bugs.
Before:
gameSchema.reducer('join_game', {}, (ctx) => {
console.log(`Player ${ctx.sender} joined`); // ❌ RUNTIME CRASH
});After:
gameSchema.reducer('join_game', {}, (ctx) => {
const sender = String(ctx.sender);
console.log("Player " + sender + " joined"); // ✅ WORKS
});no-foreach-in-reducer (error)
Problem: forEach with closures causes silent serialization failures that break table operations.
Before:
gameSchema.reducer('update_all', {}, (ctx) => {
lobbies.forEach(lobby => { // ❌ SERIALIZATION FAILURE
ctx.db.Lobby.update(lobby);
});
});After:
gameSchema.reducer('update_all', {}, (ctx) => {
for (let i = 0; i < lobbies.length; i++) { // ✅ WORKS
const lobby = lobbies[i];
ctx.db.Lobby.update(lobby);
}
});no-filter-on-table-iter (warning)
Problem: Calling .filter() on table iterators or indexed accessors causes runtime crashes.
Before:
const members = ctx.db.LobbyMembership.byLobby.filter([lobbyId]); // ❌ CRASHAfter:
const members = [];
for (const m of ctx.db.LobbyMembership.iter()) {
if (m.lobbyId === lobbyId) members.push(m); // ✅ WORKS
}no-insert-id-in-lifecycle-hooks (error)
Problem: Using Entity.insert().id in clientConnected/clientDisconnected causes TypeError: Cannot read properties of undefined (reading '__identity__').
Before:
gameSchema.clientConnected((ctx) => {
const entityId = ctx.db.Entity.insert({}).id; // ❌ CRASH
ctx.db.Position.insert({ entity_id: entityId });
});After (Option 1: Separate reducer):
gameSchema.clientConnected((ctx) => {
// Don't spawn entities here
});
gameSchema.reducer('spawn_entity', {}, (ctx) => {
const entityId = ctx.db.Entity.insert({}).id; // ✅ WORKS
ctx.db.Position.insert({ entity_id: entityId });
});After (Option 2: Scheduled reducer):
gameSchema.reducer('game_tick', ScheduledTickArgs, (ctx) => {
const entityId = ctx.db.Entity.insert({}).id; // ✅ WORKS
ctx.db.Position.insert({ entity_id: entityId });
});Why This Plugin Exists
SpacetimeDB runs reducer code in a V8 JavaScript runtime with custom serialization. Certain JavaScript patterns that work perfectly in Node.js or browsers trigger serialization bugs in SpacetimeDB's runtime, causing:
TypeError: r.charCodeAt is not a functionTypeError: Cannot convert [object Object] to a BigIntTypeError: Cannot read properties of undefined- Silent failures where operations appear to succeed but data never persists
These bugs are nearly impossible to debug because:
- Code looks correct and passes type checking
- Errors only occur at runtime after publishing to SpacetimeDB
- Error messages don't indicate the root cause
- No local development environment can reproduce them
This plugin brings these runtime failures into your IDE as red squiggly lines, making them impossible to commit.
Testing
npm testLicense
MIT
