@fadhilp/stateql 0.10.0 → 0.10.1

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
@@ -555,6 +555,15 @@ session-scoped, allocated atomically with the result, stable on cache reuse, and
555
555
  cannot be reassigned by `setAlias`; explicit aliases and all old handles continue
556
556
  to resolve.
557
557
 
558
+ Connections likewise retain canonical `conn_*` IDs and receive persistent random
559
+ 10-character lowercase base32 aliases, exposed as `alias` and `display_alias` by
560
+ `connect()` and `snapshot().connection` (optional in snapshot types for older
561
+ producers). Connection aliases are unique within the state store, allocated
562
+ atomically with the connection, and backfilled for existing records on startup.
563
+ They survive reopening; reconnecting creates a new ID and alias. They are display
564
+ identities only, separate from result aliases; internal references and lookups
565
+ continue to use canonical connection IDs.
566
+
558
567
  ### Safe profile updates
559
568
 
560
569
  ```ts
@@ -115,6 +115,17 @@ const MIGRATIONS = [
115
115
  requireIndexes(db, ["history_session_category"]);
116
116
  },
117
117
  },
118
+ {
119
+ name: "connection_aliases_v1",
120
+ apply(db) {
121
+ addColumn(db, "connections", "alias", "TEXT");
122
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS connections_alias ON connections(alias)");
123
+ },
124
+ validate(db) {
125
+ requireColumns(db, "connections", ["alias"]);
126
+ requireIndexes(db, ["connections_alias"]);
127
+ },
128
+ },
118
129
  ];
119
130
  export function runMigrations(db, now) {
120
131
  db.exec(`
@@ -223,6 +223,8 @@ export class StateQL {
223
223
  return {
224
224
  data: {
225
225
  connection_id: connection.id,
226
+ alias: connection.alias,
227
+ display_alias: connection.alias,
226
228
  driver,
227
229
  database: databaseName,
228
230
  name: connection.name,
@@ -368,6 +370,8 @@ export class StateQL {
368
370
  connection: connection
369
371
  ? {
370
372
  connection_id: connection.id,
373
+ alias: connection.alias,
374
+ display_alias: connection.alias,
371
375
  name: connection.name,
372
376
  status: "connected",
373
377
  driver: connection.driver,
@@ -11,6 +11,7 @@ export interface SessionRecord {
11
11
  }
12
12
  export interface ConnectionRecord {
13
13
  id: string;
14
+ alias?: string;
14
15
  session_id: string;
15
16
  name: string;
16
17
  driver: Driver;
@@ -166,6 +167,8 @@ export declare class StateStore {
166
167
  credentialRef?: string;
167
168
  readOnly: boolean;
168
169
  }): ConnectionRecord | undefined;
170
+ private allocateConnectionAlias;
171
+ private backfillConnectionAliases;
169
172
  getConnection(id: string): ConnectionRecord | undefined;
170
173
  activeConnection(session: SessionRecord): ConnectionRecord | undefined;
171
174
  disconnect(sessionId: string, actorId: string): boolean;
package/dist/src/store.js CHANGED
@@ -36,6 +36,7 @@ export class StateStore {
36
36
  this.db.exec("PRAGMA busy_timeout = 5000");
37
37
  this.db.exec("PRAGMA foreign_keys = ON");
38
38
  runMigrations(this.db, this.now);
39
+ this.backfillConnectionAliases();
39
40
  this.backfillGeneratedAliases();
40
41
  this.recoverStaleCommittingTransactions();
41
42
  this.deleteExpiredData();
@@ -285,6 +286,7 @@ export class StateStore {
285
286
  credential_ref, read_only, version, created_at)
286
287
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`)
287
288
  .run(id, input.sessionId, input.name, input.driver, input.databaseName, input.source, input.secretEnv ?? null, input.credentialRef ?? null, input.readOnly ? 1 : 0, timestamp);
289
+ this.allocateConnectionAlias(id);
288
290
  this.db
289
291
  .prepare(`UPDATE sessions
290
292
  SET active_connection_id = ?, updated_at = ?
@@ -298,6 +300,28 @@ export class StateStore {
298
300
  throw error;
299
301
  }
300
302
  }
303
+ allocateConnectionAlias(connectionId) {
304
+ for (let attempt = 0; attempt < 64; attempt++) {
305
+ this.db.prepare("UPDATE OR IGNORE connections SET alias = ? WHERE id = ? AND alias IS NULL").run(randomBase32Alias(), connectionId);
306
+ const connection = this.getConnection(connectionId);
307
+ if (connection?.alias)
308
+ return connection.alias;
309
+ }
310
+ throw new Error("Could not allocate a unique connection alias.");
311
+ }
312
+ backfillConnectionAliases() {
313
+ this.db.exec("BEGIN IMMEDIATE");
314
+ try {
315
+ const connections = this.db.prepare("SELECT id FROM connections WHERE alias IS NULL").all();
316
+ for (const connection of connections)
317
+ this.allocateConnectionAlias(connection.id);
318
+ this.db.exec("COMMIT");
319
+ }
320
+ catch (error) {
321
+ this.db.exec("ROLLBACK");
322
+ throw error;
323
+ }
324
+ }
301
325
  getConnection(id) {
302
326
  return this.db
303
327
  .prepare("SELECT * FROM connections WHERE id = ?")
@@ -106,6 +106,9 @@ export interface StateQLSnapshot {
106
106
  actor_id: string;
107
107
  connection: {
108
108
  connection_id: string;
109
+ /** Generated display identity; optional for older snapshot producers. */
110
+ alias?: string;
111
+ display_alias?: string;
109
112
  name: string;
110
113
  status: "connected";
111
114
  driver: Driver;
@@ -379,6 +382,9 @@ export type Row = Record<string, unknown>;
379
382
  /** Data returned by the stable, non-dynamic StateQL public methods. */
380
383
  export interface ConnectionData {
381
384
  connection_id: string;
385
+ /** Persistent generated display identity; connection_id remains canonical. */
386
+ alias: string;
387
+ display_alias: string;
382
388
  driver: Driver;
383
389
  database: string;
384
390
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fadhilp/stateql",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Stateful, agent-oriented database CLI for safe result reuse",
5
5
  "repository": {
6
6
  "type": "git",