@sandbox-agent/persist-indexeddb 0.3.1 → 0.3.2

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/dist/index.js CHANGED
@@ -22,9 +22,7 @@ var IndexedDbSessionPersistDriver = class {
22
22
  }
23
23
  async getSession(id) {
24
24
  const db = await this.dbPromise;
25
- const row = await requestToPromise(
26
- db.transaction(SESSIONS_STORE, "readonly").objectStore(SESSIONS_STORE).get(id)
27
- );
25
+ const row = await requestToPromise(db.transaction(SESSIONS_STORE, "readonly").objectStore(SESSIONS_STORE).get(id));
28
26
  if (!row || typeof row !== "object") {
29
27
  return null;
30
28
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n ListEventsRequest,\n ListPage,\n ListPageRequest,\n SessionEvent,\n SessionPersistDriver,\n SessionRecord,\n} from \"sandbox-agent\";\n\nconst DEFAULT_DB_NAME = \"sandbox-agent-session-store\";\nconst DEFAULT_DB_VERSION = 2;\nconst SESSIONS_STORE = \"sessions\";\nconst EVENTS_STORE = \"events\";\nconst EVENTS_BY_SESSION_INDEX = \"by_session_index\";\nconst DEFAULT_LIST_LIMIT = 100;\n\nexport interface IndexedDbSessionPersistDriverOptions {\n databaseName?: string;\n databaseVersion?: number;\n indexedDb?: IDBFactory;\n}\n\nexport class IndexedDbSessionPersistDriver implements SessionPersistDriver {\n private readonly indexedDb: IDBFactory;\n private readonly dbName: string;\n private readonly dbVersion: number;\n private readonly dbPromise: Promise<IDBDatabase>;\n\n constructor(options: IndexedDbSessionPersistDriverOptions = {}) {\n const indexedDb = options.indexedDb ?? globalThis.indexedDB;\n if (!indexedDb) {\n throw new Error(\"IndexedDB is not available in this runtime.\");\n }\n\n this.indexedDb = indexedDb;\n this.dbName = options.databaseName ?? DEFAULT_DB_NAME;\n this.dbVersion = options.databaseVersion ?? DEFAULT_DB_VERSION;\n this.dbPromise = this.openDatabase();\n }\n\n async getSession(id: string): Promise<SessionRecord | null> {\n const db = await this.dbPromise;\n const row = await requestToPromise<IDBValidKey | SessionRow | undefined>(\n db.transaction(SESSIONS_STORE, \"readonly\").objectStore(SESSIONS_STORE).get(id),\n );\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return decodeSessionRow(row as SessionRow);\n }\n\n async listSessions(request: ListPageRequest = {}): Promise<ListPage<SessionRecord>> {\n const db = await this.dbPromise;\n const rows = await getAllRows<SessionRow>(db, SESSIONS_STORE);\n\n rows.sort((a, b) => {\n if (a.createdAt !== b.createdAt) {\n return a.createdAt - b.createdAt;\n }\n return a.id.localeCompare(b.id);\n });\n\n const offset = parseCursor(request.cursor);\n const limit = normalizeLimit(request.limit);\n const slice = rows.slice(offset, offset + limit).map(decodeSessionRow);\n const nextOffset = offset + slice.length;\n\n return {\n items: slice,\n nextCursor: nextOffset < rows.length ? String(nextOffset) : undefined,\n };\n }\n\n async updateSession(session: SessionRecord): Promise<void> {\n const db = await this.dbPromise;\n await transactionPromise(db, [SESSIONS_STORE], \"readwrite\", (tx) => {\n tx.objectStore(SESSIONS_STORE).put(encodeSessionRow(session));\n });\n }\n\n async listEvents(request: ListEventsRequest): Promise<ListPage<SessionEvent>> {\n const db = await this.dbPromise;\n const rows = (await getAllRows<EventRow>(db, EVENTS_STORE))\n .filter((row) => row.sessionId === request.sessionId)\n .sort(compareEventRowsByOrder);\n\n const offset = parseCursor(request.cursor);\n const limit = normalizeLimit(request.limit);\n const slice = rows.slice(offset, offset + limit).map(decodeEventRow);\n const nextOffset = offset + slice.length;\n\n return {\n items: slice,\n nextCursor: nextOffset < rows.length ? String(nextOffset) : undefined,\n };\n }\n\n async insertEvent(event: SessionEvent): Promise<void> {\n const db = await this.dbPromise;\n await transactionPromise(db, [EVENTS_STORE], \"readwrite\", (tx) => {\n tx.objectStore(EVENTS_STORE).put(encodeEventRow(event));\n });\n }\n\n async close(): Promise<void> {\n const db = await this.dbPromise;\n db.close();\n }\n\n private openDatabase(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = this.indexedDb.open(this.dbName, this.dbVersion);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n\n if (!db.objectStoreNames.contains(SESSIONS_STORE)) {\n db.createObjectStore(SESSIONS_STORE, { keyPath: \"id\" });\n }\n\n if (!db.objectStoreNames.contains(EVENTS_STORE)) {\n const events = db.createObjectStore(EVENTS_STORE, { keyPath: \"id\" });\n events.createIndex(EVENTS_BY_SESSION_INDEX, [\"sessionId\", \"eventIndex\", \"id\"], {\n unique: false,\n });\n } else {\n const tx = request.transaction;\n if (!tx) {\n return;\n }\n const events = tx.objectStore(EVENTS_STORE);\n if (!events.indexNames.contains(EVENTS_BY_SESSION_INDEX)) {\n events.createIndex(EVENTS_BY_SESSION_INDEX, [\"sessionId\", \"eventIndex\", \"id\"], {\n unique: false,\n });\n }\n }\n };\n\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error(\"Unable to open IndexedDB\"));\n });\n }\n}\n\ntype SessionRow = {\n id: string;\n agent: string;\n agentSessionId: string;\n lastConnectionId: string;\n createdAt: number;\n destroyedAt?: number;\n sessionInit?: SessionRecord[\"sessionInit\"];\n};\n\ntype EventRow = {\n id: number | string;\n eventIndex?: number;\n sessionId: string;\n createdAt: number;\n connectionId: string;\n sender: \"client\" | \"agent\";\n payload: unknown;\n};\n\nfunction encodeSessionRow(session: SessionRecord): SessionRow {\n return {\n id: session.id,\n agent: session.agent,\n agentSessionId: session.agentSessionId,\n lastConnectionId: session.lastConnectionId,\n createdAt: session.createdAt,\n destroyedAt: session.destroyedAt,\n sessionInit: session.sessionInit,\n };\n}\n\nfunction decodeSessionRow(row: SessionRow): SessionRecord {\n return {\n id: row.id,\n agent: row.agent,\n agentSessionId: row.agentSessionId,\n lastConnectionId: row.lastConnectionId,\n createdAt: row.createdAt,\n destroyedAt: row.destroyedAt,\n sessionInit: row.sessionInit,\n };\n}\n\nfunction encodeEventRow(event: SessionEvent): EventRow {\n return {\n id: event.id,\n eventIndex: event.eventIndex,\n sessionId: event.sessionId,\n createdAt: event.createdAt,\n connectionId: event.connectionId,\n sender: event.sender,\n payload: event.payload,\n };\n}\n\nfunction decodeEventRow(row: EventRow): SessionEvent {\n return {\n id: String(row.id),\n eventIndex: parseEventIndex(row.eventIndex, row.id),\n sessionId: row.sessionId,\n createdAt: row.createdAt,\n connectionId: row.connectionId,\n sender: row.sender,\n payload: row.payload as SessionEvent[\"payload\"],\n };\n}\n\nasync function getAllRows<T>(db: IDBDatabase, storeName: string): Promise<T[]> {\n return await transactionPromise<T[]>(db, [storeName], \"readonly\", async (tx) => {\n const request = tx.objectStore(storeName).getAll();\n return (await requestToPromise(request)) as T[];\n });\n}\n\nfunction normalizeLimit(limit: number | undefined): number {\n if (!Number.isFinite(limit) || (limit ?? 0) < 1) {\n return DEFAULT_LIST_LIMIT;\n }\n return Math.floor(limit as number);\n}\n\nfunction parseCursor(cursor: string | undefined): number {\n if (!cursor) {\n return 0;\n }\n const parsed = Number.parseInt(cursor, 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return 0;\n }\n return parsed;\n}\n\nfunction compareEventRowsByOrder(a: EventRow, b: EventRow): number {\n const indexA = parseEventIndex(a.eventIndex, a.id);\n const indexB = parseEventIndex(b.eventIndex, b.id);\n if (indexA !== indexB) {\n return indexA - indexB;\n }\n return String(a.id).localeCompare(String(b.id));\n}\n\nfunction parseEventIndex(value: number | undefined, fallback: number | string): number {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return Math.max(0, Math.floor(value));\n }\n\n const parsed = Number.parseInt(String(fallback), 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return 0;\n }\n return parsed;\n}\n\nfunction requestToPromise<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error(\"IndexedDB request failed\"));\n });\n}\n\nfunction transactionPromise<T>(\n db: IDBDatabase,\n stores: string[],\n mode: IDBTransactionMode,\n run: (tx: IDBTransaction) => T | Promise<T>,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(stores, mode);\n let settled = false;\n let resultValue: T | undefined;\n let runCompleted = false;\n let txCompleted = false;\n\n function tryResolve() {\n if (settled || !runCompleted || !txCompleted) {\n return;\n }\n settled = true;\n resolve(resultValue as T);\n }\n\n tx.oncomplete = () => {\n txCompleted = true;\n tryResolve();\n };\n\n tx.onerror = () => {\n if (settled) {\n return;\n }\n settled = true;\n reject(tx.error ?? new Error(\"IndexedDB transaction failed\"));\n };\n\n tx.onabort = () => {\n if (settled) {\n return;\n }\n settled = true;\n reject(tx.error ?? new Error(\"IndexedDB transaction aborted\"));\n };\n\n Promise.resolve(run(tx))\n .then((value) => {\n resultValue = value;\n runCompleted = true;\n tryResolve();\n })\n .catch((error) => {\n if (!settled) {\n settled = true;\n reject(error);\n }\n try {\n tx.abort();\n } catch {\n // no-op\n }\n });\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { ListEventsRequest, ListPage, ListPageRequest, SessionEvent, SessionPersistDriver, SessionRecord } from \"sandbox-agent\";\n\nconst DEFAULT_DB_NAME = \"sandbox-agent-session-store\";\nconst DEFAULT_DB_VERSION = 2;\nconst SESSIONS_STORE = \"sessions\";\nconst EVENTS_STORE = \"events\";\nconst EVENTS_BY_SESSION_INDEX = \"by_session_index\";\nconst DEFAULT_LIST_LIMIT = 100;\n\nexport interface IndexedDbSessionPersistDriverOptions {\n databaseName?: string;\n databaseVersion?: number;\n indexedDb?: IDBFactory;\n}\n\nexport class IndexedDbSessionPersistDriver implements SessionPersistDriver {\n private readonly indexedDb: IDBFactory;\n private readonly dbName: string;\n private readonly dbVersion: number;\n private readonly dbPromise: Promise<IDBDatabase>;\n\n constructor(options: IndexedDbSessionPersistDriverOptions = {}) {\n const indexedDb = options.indexedDb ?? globalThis.indexedDB;\n if (!indexedDb) {\n throw new Error(\"IndexedDB is not available in this runtime.\");\n }\n\n this.indexedDb = indexedDb;\n this.dbName = options.databaseName ?? DEFAULT_DB_NAME;\n this.dbVersion = options.databaseVersion ?? DEFAULT_DB_VERSION;\n this.dbPromise = this.openDatabase();\n }\n\n async getSession(id: string): Promise<SessionRecord | null> {\n const db = await this.dbPromise;\n const row = await requestToPromise<IDBValidKey | SessionRow | undefined>(db.transaction(SESSIONS_STORE, \"readonly\").objectStore(SESSIONS_STORE).get(id));\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return decodeSessionRow(row as SessionRow);\n }\n\n async listSessions(request: ListPageRequest = {}): Promise<ListPage<SessionRecord>> {\n const db = await this.dbPromise;\n const rows = await getAllRows<SessionRow>(db, SESSIONS_STORE);\n\n rows.sort((a, b) => {\n if (a.createdAt !== b.createdAt) {\n return a.createdAt - b.createdAt;\n }\n return a.id.localeCompare(b.id);\n });\n\n const offset = parseCursor(request.cursor);\n const limit = normalizeLimit(request.limit);\n const slice = rows.slice(offset, offset + limit).map(decodeSessionRow);\n const nextOffset = offset + slice.length;\n\n return {\n items: slice,\n nextCursor: nextOffset < rows.length ? String(nextOffset) : undefined,\n };\n }\n\n async updateSession(session: SessionRecord): Promise<void> {\n const db = await this.dbPromise;\n await transactionPromise(db, [SESSIONS_STORE], \"readwrite\", (tx) => {\n tx.objectStore(SESSIONS_STORE).put(encodeSessionRow(session));\n });\n }\n\n async listEvents(request: ListEventsRequest): Promise<ListPage<SessionEvent>> {\n const db = await this.dbPromise;\n const rows = (await getAllRows<EventRow>(db, EVENTS_STORE)).filter((row) => row.sessionId === request.sessionId).sort(compareEventRowsByOrder);\n\n const offset = parseCursor(request.cursor);\n const limit = normalizeLimit(request.limit);\n const slice = rows.slice(offset, offset + limit).map(decodeEventRow);\n const nextOffset = offset + slice.length;\n\n return {\n items: slice,\n nextCursor: nextOffset < rows.length ? String(nextOffset) : undefined,\n };\n }\n\n async insertEvent(event: SessionEvent): Promise<void> {\n const db = await this.dbPromise;\n await transactionPromise(db, [EVENTS_STORE], \"readwrite\", (tx) => {\n tx.objectStore(EVENTS_STORE).put(encodeEventRow(event));\n });\n }\n\n async close(): Promise<void> {\n const db = await this.dbPromise;\n db.close();\n }\n\n private openDatabase(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = this.indexedDb.open(this.dbName, this.dbVersion);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n\n if (!db.objectStoreNames.contains(SESSIONS_STORE)) {\n db.createObjectStore(SESSIONS_STORE, { keyPath: \"id\" });\n }\n\n if (!db.objectStoreNames.contains(EVENTS_STORE)) {\n const events = db.createObjectStore(EVENTS_STORE, { keyPath: \"id\" });\n events.createIndex(EVENTS_BY_SESSION_INDEX, [\"sessionId\", \"eventIndex\", \"id\"], {\n unique: false,\n });\n } else {\n const tx = request.transaction;\n if (!tx) {\n return;\n }\n const events = tx.objectStore(EVENTS_STORE);\n if (!events.indexNames.contains(EVENTS_BY_SESSION_INDEX)) {\n events.createIndex(EVENTS_BY_SESSION_INDEX, [\"sessionId\", \"eventIndex\", \"id\"], {\n unique: false,\n });\n }\n }\n };\n\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error(\"Unable to open IndexedDB\"));\n });\n }\n}\n\ntype SessionRow = {\n id: string;\n agent: string;\n agentSessionId: string;\n lastConnectionId: string;\n createdAt: number;\n destroyedAt?: number;\n sessionInit?: SessionRecord[\"sessionInit\"];\n};\n\ntype EventRow = {\n id: number | string;\n eventIndex?: number;\n sessionId: string;\n createdAt: number;\n connectionId: string;\n sender: \"client\" | \"agent\";\n payload: unknown;\n};\n\nfunction encodeSessionRow(session: SessionRecord): SessionRow {\n return {\n id: session.id,\n agent: session.agent,\n agentSessionId: session.agentSessionId,\n lastConnectionId: session.lastConnectionId,\n createdAt: session.createdAt,\n destroyedAt: session.destroyedAt,\n sessionInit: session.sessionInit,\n };\n}\n\nfunction decodeSessionRow(row: SessionRow): SessionRecord {\n return {\n id: row.id,\n agent: row.agent,\n agentSessionId: row.agentSessionId,\n lastConnectionId: row.lastConnectionId,\n createdAt: row.createdAt,\n destroyedAt: row.destroyedAt,\n sessionInit: row.sessionInit,\n };\n}\n\nfunction encodeEventRow(event: SessionEvent): EventRow {\n return {\n id: event.id,\n eventIndex: event.eventIndex,\n sessionId: event.sessionId,\n createdAt: event.createdAt,\n connectionId: event.connectionId,\n sender: event.sender,\n payload: event.payload,\n };\n}\n\nfunction decodeEventRow(row: EventRow): SessionEvent {\n return {\n id: String(row.id),\n eventIndex: parseEventIndex(row.eventIndex, row.id),\n sessionId: row.sessionId,\n createdAt: row.createdAt,\n connectionId: row.connectionId,\n sender: row.sender,\n payload: row.payload as SessionEvent[\"payload\"],\n };\n}\n\nasync function getAllRows<T>(db: IDBDatabase, storeName: string): Promise<T[]> {\n return await transactionPromise<T[]>(db, [storeName], \"readonly\", async (tx) => {\n const request = tx.objectStore(storeName).getAll();\n return (await requestToPromise(request)) as T[];\n });\n}\n\nfunction normalizeLimit(limit: number | undefined): number {\n if (!Number.isFinite(limit) || (limit ?? 0) < 1) {\n return DEFAULT_LIST_LIMIT;\n }\n return Math.floor(limit as number);\n}\n\nfunction parseCursor(cursor: string | undefined): number {\n if (!cursor) {\n return 0;\n }\n const parsed = Number.parseInt(cursor, 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return 0;\n }\n return parsed;\n}\n\nfunction compareEventRowsByOrder(a: EventRow, b: EventRow): number {\n const indexA = parseEventIndex(a.eventIndex, a.id);\n const indexB = parseEventIndex(b.eventIndex, b.id);\n if (indexA !== indexB) {\n return indexA - indexB;\n }\n return String(a.id).localeCompare(String(b.id));\n}\n\nfunction parseEventIndex(value: number | undefined, fallback: number | string): number {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return Math.max(0, Math.floor(value));\n }\n\n const parsed = Number.parseInt(String(fallback), 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return 0;\n }\n return parsed;\n}\n\nfunction requestToPromise<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error(\"IndexedDB request failed\"));\n });\n}\n\nfunction transactionPromise<T>(db: IDBDatabase, stores: string[], mode: IDBTransactionMode, run: (tx: IDBTransaction) => T | Promise<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(stores, mode);\n let settled = false;\n let resultValue: T | undefined;\n let runCompleted = false;\n let txCompleted = false;\n\n function tryResolve() {\n if (settled || !runCompleted || !txCompleted) {\n return;\n }\n settled = true;\n resolve(resultValue as T);\n }\n\n tx.oncomplete = () => {\n txCompleted = true;\n tryResolve();\n };\n\n tx.onerror = () => {\n if (settled) {\n return;\n }\n settled = true;\n reject(tx.error ?? new Error(\"IndexedDB transaction failed\"));\n };\n\n tx.onabort = () => {\n if (settled) {\n return;\n }\n settled = true;\n reject(tx.error ?? new Error(\"IndexedDB transaction aborted\"));\n };\n\n Promise.resolve(run(tx))\n .then((value) => {\n resultValue = value;\n runCompleted = true;\n tryResolve();\n })\n .catch((error) => {\n if (!settled) {\n settled = true;\n reject(error);\n }\n try {\n tx.abort();\n } catch {\n // no-op\n }\n });\n });\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandbox-agent/persist-indexeddb",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "IndexedDB persistence driver for the Sandbox Agent TypeScript SDK",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -17,7 +17,7 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "sandbox-agent": "0.3.1"
20
+ "sandbox-agent": "0.3.2"
21
21
  },
22
22
  "files": [
23
23
  "dist"