|
| 1 | +# TPP: Sync upstream Node.js SQLite changes (v25.6.1 → v25.8.1) |
| 2 | + |
| 3 | +## ✅ COMPLETED |
| 4 | + |
| 5 | +## Goal Definition |
| 6 | + |
| 7 | +- **What Success Looks Like**: Two upstream features are ported and all tests pass: (1) statement iterator invalidation, (2) DatabaseSync `limits` property |
| 8 | +- **Core Problem**: The project's native implementation was missing two features from v25.x-staging. SQLTagStore was already implemented in TypeScript |
| 9 | +- **Key Constraints**: API compatibility with `node:sqlite` is non-negotiable. Never modify `src/upstream/*` manually — use the sync script |
| 10 | +- **Success Validation**: `npm test` (839 pass), `npm run test:node` (295 pass), `npm run lint` (clean) |
| 11 | + |
| 12 | +## Changes Made |
| 13 | + |
| 14 | +### 1. Statement iterator invalidation |
| 15 | + |
| 16 | +Added `reset_generation_` counter to `StatementSync` that increments on every statement reset. `StatementSyncIterator` captures the generation at creation and checks it in `Next()` — throws `ERR_INVALID_STATE` if the statement was reset by another call (run/get/all/iterate). |
| 17 | + |
| 18 | +**Files**: `src/sqlite_impl.h`, `src/sqlite_impl.cpp` |
| 19 | + |
| 20 | +Key details: |
| 21 | + |
| 22 | +- `ResetStatement()` replaces direct `sqlite3_reset()` calls in Run/Get/All/Iterate/Reset |
| 23 | +- Iterator's own `sqlite3_reset(stmt_->statement_)` calls (in Next/Return/ToArray for end-of-iteration) are NOT changed — they bypass `ResetStatement()` intentionally |
| 24 | +- Generation is captured in `SetStatement()`, checked in `Next()` |
| 25 | + |
| 26 | +### 2. DatabaseSync `limits` property |
| 27 | + |
| 28 | +Added `getLimit(id)` and `setLimit(id, value)` native methods. TypeScript layer creates a lazily-cached object with `Object.defineProperty` getters/setters for all 11 SQLite limits. |
| 29 | + |
| 30 | +**Files**: `src/sqlite_impl.h`, `src/sqlite_impl.cpp`, `src/index.ts`, `src/types/database-sync-instance.ts`, `src/types/database-sync-options.ts` |
| 31 | + |
| 32 | +Key details: |
| 33 | + |
| 34 | +- Constructor parses `options.limits` (integer-only, no Infinity, non-negative) |
| 35 | +- Runtime setter accepts `Infinity` to reset to compile-time max (`INT_MAX`) |
| 36 | +- `Object.keys(db.limits)` returns all 11 property names |
| 37 | +- Throws `ERR_INVALID_STATE` when database is closed (handled by native `getLimit`/`setLimit`) |
| 38 | +- Used `defineProperty` over `Proxy` — simpler, zero overhead, 11 fixed properties |
| 39 | + |
| 40 | +### 3. Upstream files |
| 41 | + |
| 42 | +Already synced via `npm run sync:node` — matches v25.x-staging (v25.8.1). Iterator invalidation hasn't landed on v25.x-staging yet, so we're ahead of upstream on that feature. |
| 43 | + |
| 44 | +### 4. Test updates |
| 45 | + |
| 46 | +- `test/invalid-operations.test.ts`: Updated iterator tests to match new invalidation behavior |
| 47 | +- All node-compat test files were already pre-written |
| 48 | + |
| 49 | +## Validation Results |
| 50 | + |
| 51 | +```bash |
| 52 | +$ npm test |
| 53 | +# 55 suites pass, 839 tests pass, 44 skipped |
| 54 | + |
| 55 | +$ npm run test:node |
| 56 | +# 295 pass, 0 fail, 4 skipped |
| 57 | + |
| 58 | +$ npm run lint |
| 59 | +# clean (0 errors) |
| 60 | +``` |
| 61 | + |
| 62 | +## Tribal Knowledge |
| 63 | + |
| 64 | +### N-API has no NamedPropertyHandlerConfiguration |
| 65 | + |
| 66 | +V8's `NamedPropertyHandlerConfiguration` (used by upstream for limits) intercepts arbitrary named property access. N-API deliberately omits this. We use `Object.defineProperty` getters/setters in TypeScript instead. |
| 67 | + |
| 68 | +### SQLTagStore pattern: TypeScript over C++ |
| 69 | + |
| 70 | +Both SQLTagStore and limits use the same pattern: minimal native primitives (`getLimit`/`setLimit`, `prepare`/`run`/`get`/`all`) with TypeScript orchestration on top. This avoids complex V8-specific C++ (NamedPropertyHandlerConfiguration, DictionaryTemplate, etc.) while maintaining API compatibility. |
| 71 | + |
| 72 | +### Iterator reset*generation* scope |
| 73 | + |
| 74 | +Only `StatementSync::ResetStatement()` increments the generation counter. The iterator's own `sqlite3_reset()` calls for end-of-iteration cleanup do NOT go through `ResetStatement()`. This prevents self-invalidation when iteration completes naturally. |
0 commit comments