latticesql 1.8.1 → 1.10.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/README.md CHANGED
@@ -77,10 +77,10 @@ npm install latticesql
77
77
 
78
78
  Requires **Node.js 18+**. The default backend is SQLite (`better-sqlite3`) — no external database process needed.
79
79
 
80
- To use the Postgres backend (for Supabase, Neon, RDS, or any other Postgres-compatible database), install the optional dependencies:
80
+ To use the Postgres backend (for Supabase, Neon, RDS, or any other Postgres-compatible database), install the optional dependency:
81
81
 
82
82
  ```bash
83
- npm install latticesql pg synckit
83
+ npm install latticesql pg
84
84
  ```
85
85
 
86
86
  Then pass a connection string instead of a file path:
@@ -2153,12 +2153,14 @@ The rest of the API — `define()`, `init()`, `query()`, `insert()`, `render()`,
2153
2153
 
2154
2154
  ### Postgres setup
2155
2155
 
2156
- `PostgresAdapter` depends on `pg` and `synckit`. Both are listed as `optionalDependencies`, so SQLite-only consumers don't pay the install cost. Install them when you actually use Postgres:
2156
+ `PostgresAdapter` depends on `pg`, listed as an `optionalDependency` so SQLite-only consumers don't pay the install cost. Install it when you actually use Postgres:
2157
2157
 
2158
2158
  ```bash
2159
- npm install pg synckit
2159
+ npm install pg
2160
2160
  ```
2161
2161
 
2162
+ > **Migrating from `<= 1.9.x`?** `synckit` is no longer a dependency. Drop it from your install. The `dist/postgres-worker.cjs` file is also gone (it served the now-removed sync surface).
2163
+
2162
2164
  Then point Lattice at any Postgres-compatible database that speaks the standard wire protocol on port 5432:
2163
2165
 
2164
2166
  ```ts
@@ -2166,16 +2168,13 @@ const lattice = new Lattice('postgres://user:pass@host:5432/db');
2166
2168
  await lattice.init();
2167
2169
  ```
2168
2170
 
2169
- **Connection pooler note:** the `PostgresAdapter` exposes both a synckit-bridged sync surface and a native `pg.Pool`-backed async surface (since 1.8.0). Pick your pooler endpoint based on which surface you primarily use:
2170
-
2171
- - **Transaction-mode pooling** (e.g. PgBouncer transaction-mode, Supabase port `6543`) — recommended for `pg.Pool`-backed callers. The async surface (`runAsync`/`getAsync`/`allAsync`/`withClient`) is designed for transaction-mode: server-side prepared statements aren't kept across calls (because the upstream connection returns to the pool at `COMMIT`), and `prepareAsync` re-binds per call, so the prepared-statement-incompatibility caveat doesn't apply. Migrations are wrapped in `withClient(fn)` and acquire a transaction-scoped advisory lock so concurrent app boots serialize cleanly.
2172
- - **Session-mode pooling** (e.g. PgBouncer session-mode, Supabase port `5432`) — required if you rely on the synckit-bridged sync surface (`adapter.run`/`adapter.prepare`) for transactional code. The synckit worker owns a single `pg.Client`, so session-mode preserves the per-connection guarantees that raw `BEGIN`/`COMMIT` calls assume.
2171
+ **Recommended pooler:** **transaction-mode** (e.g. PgBouncer transaction-mode, Supabase port `6543`). `PostgresAdapter` is native against `pg.Pool` and designed for transaction-mode: server-side prepared statements aren't kept across calls (because the upstream connection returns to the pool at `COMMIT`), and `prepareAsync` re-binds per call. Migrations are wrapped in `withClient(fn)` and acquire a transaction-scoped advisory lock so concurrent app boots serialize cleanly. `pool.max` is configurable via `PostgresAdapterOptions.poolSize` (default 10).
2173
2172
 
2174
- **Two surfaces, one adapter (since 1.8.0):**
2173
+ **Async-only on Postgres (since 1.10.0):**
2175
2174
 
2176
- - **Sync surface** (`run` / `get` / `all` / `prepare`) bridged via a synckit worker thread that owns a single `pg.Client`. The main thread blocks on `Atomics.wait` until the worker replies. Each query pays ~1–3 ms of message-passing overhead. Used by callers that haven't migrated to the async surface yet; preserved for back-compat.
2177
- - **Async surface** (`runAsync` / `getAsync` / `allAsync` / `prepareAsync` / `withClient`) — native against a `pg.Pool` in the main thread. No synckit, no `Atomics.wait`. The Node event loop is free to handle other work between awaited DB roundtrips. Required for any workload that runs long sync bursts on the main thread; `pool.max` is configurable via `PostgresAdapterOptions.poolSize` (default 10).
2178
- - **Transactional contract**: any code that issues `BEGIN`/`COMMIT` should use `withClient(fn)` rather than raw `adapter.run('BEGIN')`. The pool checks out a single connection for the lifetime of `fn` and the `TxClient` handed to `fn` pins every query to that connection. Raw `adapter.run('BEGIN')` is only safe under the synckit worker (single-connection by construction); the next major release will remove the synckit worker entirely.
2175
+ - The async surface (`runAsync` / `getAsync` / `allAsync` / `prepareAsync` / `introspectColumnsAsync` / `addColumnAsync` / `withClient`) is the *only* path that does work against Postgres. The synchronous methods (`run` / `get` / `all` / `prepare` / `introspectColumns` / `addColumn`) **throw** with a clear error pointing at the async equivalent. `pg.Pool` is fundamentally async; the previous synckit-bridged sync surface was a workaround that blocked the Node main thread on `Atomics.wait`, and it was removed in 1.10.0 once lattice core had migrated to async at every call site (1.9.0).
2176
+ - `SQLiteAdapter` keeps the sync surface as the authoritative path (better-sqlite3 is sync by design). Its async methods just wrap the sync calls in resolved Promises the one-microtask cost is negligible relative to having a single cross-dialect code path.
2177
+ - **Transactional contract**: any code that issues `BEGIN`/`COMMIT` should use `withClient(fn)`. The pool checks out a single connection for the lifetime of `fn` and the `TxClient` handed to `fn` pins every query to that connection. Raw `adapter.runAsync('BEGIN')` is unsafe different awaited calls land on different upstream connections under transaction-mode pooling.
2179
2178
 
2180
2179
  ```ts
2181
2180
  // Recommended pattern for transactional writes
@@ -2202,7 +2201,9 @@ export interface StorageAdapter {
2202
2201
  // core. Most application code never needs to read this.
2203
2202
  readonly dialect: 'sqlite' | 'postgres';
2204
2203
 
2205
- // Sync surface — required.
2204
+ // Sync surface — required by the interface. Sync-native backends like
2205
+ // SQLite implement it; async-native backends like Postgres (since 1.10.0)
2206
+ // throw with a helpful error pointing callers at the async equivalents.
2206
2207
  run(sql: string, params?: unknown[]): void;
2207
2208
  get(sql: string, params?: unknown[]): Row | undefined;
2208
2209
  all(sql: string, params?: unknown[]): Row[];
@@ -2217,6 +2218,8 @@ export interface StorageAdapter {
2217
2218
  getAsync?(sql: string, params?: unknown[]): Promise<Row | undefined>;
2218
2219
  allAsync?(sql: string, params?: unknown[]): Promise<Row[]>;
2219
2220
  prepareAsync?(sql: string): PreparedStatementAsync;
2221
+ introspectColumnsAsync?(table: string): Promise<string[]>;
2222
+ addColumnAsync?(table: string, column: string, typeSpec: string): Promise<void>;
2220
2223
  withClient?<T>(fn: (tx: TxClient) => Promise<T>): Promise<T>;
2221
2224
  }
2222
2225
  ```