diff --git a/apps/gateway/package.json b/apps/gateway/package.json index aa63729..168a3ed 100644 --- a/apps/gateway/package.json +++ b/apps/gateway/package.json @@ -1,6 +1,6 @@ { "name": "@mosaic/gateway", - "version": "0.0.3", + "version": "0.1.0", "repository": { "type": "git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", diff --git a/package.json b/package.json index 3140271..ba446e4 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,5 @@ "turbo": "^2.0.0", "typescript": "^5.8.0", "vitest": "^2.0.0" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "better-sqlite3" - ] } } diff --git a/packages/cli/src/commands/gateway/install.ts b/packages/cli/src/commands/gateway/install.ts index 241ea92..17b2ca2 100644 --- a/packages/cli/src/commands/gateway/install.ts +++ b/packages/cli/src/commands/gateway/install.ts @@ -121,7 +121,7 @@ async function doInstall(rl: ReturnType, opts: InstallOp tier === 'local' ? { tier: 'local', - storage: { type: 'sqlite', path: join(GATEWAY_HOME, 'data.db') }, + storage: { type: 'pglite', dataDir: join(GATEWAY_HOME, 'storage-pglite') }, queue: { type: 'local', dataDir: join(GATEWAY_HOME, 'queue') }, memory: { type: 'keyword' }, } diff --git a/packages/storage/package.json b/packages/storage/package.json index c7884d2..5451fd1 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -1,6 +1,6 @@ { "name": "@mosaic/storage", - "version": "0.0.2", + "version": "0.0.3", "repository": { "type": "git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", @@ -21,12 +21,11 @@ "test": "vitest run --passWithNoTests" }, "dependencies": { + "@electric-sql/pglite": "^0.2.17", "@mosaic/db": "workspace:^", - "@mosaic/types": "workspace:*", - "better-sqlite3": "^12.8.0" + "@mosaic/types": "workspace:*" }, "devDependencies": { - "@types/better-sqlite3": "^7.6.13", "typescript": "^5.8.0", "vitest": "^2.0.0" }, diff --git a/packages/storage/src/adapters/sqlite.test.ts b/packages/storage/src/adapters/pglite.test.ts similarity index 95% rename from packages/storage/src/adapters/sqlite.test.ts rename to packages/storage/src/adapters/pglite.test.ts index 85f524b..f8c5363 100644 --- a/packages/storage/src/adapters/sqlite.test.ts +++ b/packages/storage/src/adapters/pglite.test.ts @@ -1,11 +1,12 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { SqliteAdapter } from './sqlite.js'; +import { PgliteAdapter } from './pglite.js'; -describe('SqliteAdapter', () => { - let adapter: SqliteAdapter; +describe('PgliteAdapter', () => { + let adapter: PgliteAdapter; beforeEach(async () => { - adapter = new SqliteAdapter({ type: 'sqlite', path: ':memory:' }); + // In-memory PGlite instance — no dataDir = memory mode + adapter = new PgliteAdapter({ type: 'pglite' }); await adapter.migrate(); }); @@ -80,7 +81,7 @@ describe('SqliteAdapter', () => { it('supports limit and offset', async () => { for (let i = 0; i < 5; i++) { - await adapter.create('users', { name: `User${i}`, idx: i }); + await adapter.create('users', { name: `User${i.toString()}`, idx: i }); } const page = await adapter.find('users', undefined, { diff --git a/packages/storage/src/adapters/pglite.ts b/packages/storage/src/adapters/pglite.ts new file mode 100644 index 0000000..fe00e54 --- /dev/null +++ b/packages/storage/src/adapters/pglite.ts @@ -0,0 +1,290 @@ +import { PGlite } from '@electric-sql/pglite'; +import { randomUUID } from 'node:crypto'; +import type { StorageAdapter, StorageConfig } from '../types.js'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +const COLLECTIONS = [ + 'users', + 'sessions', + 'accounts', + 'projects', + 'missions', + 'tasks', + 'agents', + 'conversations', + 'messages', + 'preferences', + 'insights', + 'skills', + 'events', + 'routing_rules', + 'provider_credentials', + 'agent_logs', + 'teams', + 'team_members', + 'mission_tasks', + 'tickets', + 'summarization_jobs', + 'appreciations', + 'verifications', +] as const; + +function buildFilterClause(filter?: Record): { + clause: string; + params: unknown[]; +} { + if (!filter || Object.keys(filter).length === 0) return { clause: '', params: [] }; + const conditions: string[] = []; + const params: unknown[] = []; + let paramIdx = 1; + for (const [key, value] of Object.entries(filter)) { + if (key === 'id') { + conditions.push(`id = $${paramIdx.toString()}`); + params.push(value); + paramIdx++; + } else { + conditions.push(`data->>'${key}' = $${paramIdx.toString()}`); + params.push(typeof value === 'object' ? JSON.stringify(value) : value); + paramIdx++; + } + } + return { clause: ` WHERE ${conditions.join(' AND ')}`, params }; +} + +type PgClient = PGlite | { query: PGlite['query'] }; + +async function pgCreate>( + pg: PgClient, + collection: string, + data: T, +): Promise { + const id = (data as any).id ?? randomUUID(); + const rest = Object.fromEntries(Object.entries(data).filter(([k]) => k !== 'id')); + await pg.query(`INSERT INTO ${collection} (id, data) VALUES ($1, $2::jsonb)`, [ + id, + JSON.stringify(rest), + ]); + return { ...data, id } as T & { id: string }; +} + +async function pgRead>( + pg: PgClient, + collection: string, + id: string, +): Promise { + const result = await pg.query<{ id: string; data: Record }>( + `SELECT id, data FROM ${collection} WHERE id = $1`, + [id], + ); + const row = result.rows[0]; + if (!row) return null; + return { id: row.id, ...(row.data as object) } as unknown as T; +} + +async function pgUpdate( + pg: PgClient, + collection: string, + id: string, + data: Record, +): Promise { + const existing = await pg.query<{ data: Record }>( + `SELECT data FROM ${collection} WHERE id = $1`, + [id], + ); + const row = existing.rows[0]; + if (!row) return false; + const merged = { ...(row.data as object), ...data }; + const result = await pg.query( + `UPDATE ${collection} SET data = $1::jsonb, updated_at = now() WHERE id = $2`, + [JSON.stringify(merged), id], + ); + return (result.affectedRows ?? 0) > 0; +} + +async function pgDelete(pg: PgClient, collection: string, id: string): Promise { + const result = await pg.query(`DELETE FROM ${collection} WHERE id = $1`, [id]); + return (result.affectedRows ?? 0) > 0; +} + +async function pgFind>( + pg: PgClient, + collection: string, + filter?: Record, + opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' }, +): Promise { + const { clause, params } = buildFilterClause(filter); + let paramIdx = params.length + 1; + let query = `SELECT id, data FROM ${collection}${clause}`; + if (opts?.orderBy) { + const dir = opts.order === 'desc' ? 'DESC' : 'ASC'; + const col = + opts.orderBy === 'id' + ? 'id' + : opts.orderBy === 'created_at' || opts.orderBy === 'updated_at' + ? opts.orderBy + : `data->>'${opts.orderBy}'`; + query += ` ORDER BY ${col} ${dir}`; + } + if (opts?.limit !== undefined) { + query += ` LIMIT $${paramIdx.toString()}`; + params.push(opts.limit); + paramIdx++; + } + if (opts?.offset !== undefined) { + query += ` OFFSET $${paramIdx.toString()}`; + params.push(opts.offset); + paramIdx++; + } + const result = await pg.query<{ id: string; data: Record }>(query, params); + return result.rows.map((row) => ({ id: row.id, ...(row.data as object) }) as unknown as T); +} + +async function pgCount( + pg: PgClient, + collection: string, + filter?: Record, +): Promise { + const { clause, params } = buildFilterClause(filter); + const result = await pg.query<{ count: string }>( + `SELECT COUNT(*) as count FROM ${collection}${clause}`, + params, + ); + return parseInt(result.rows[0]?.count ?? '0', 10); +} + +export class PgliteAdapter implements StorageAdapter { + readonly name = 'pglite'; + private pg: PGlite; + + constructor(config: Extract) { + this.pg = new PGlite(config.dataDir); + } + + async create>( + collection: string, + data: T, + ): Promise { + return pgCreate(this.pg, collection, data); + } + + async read>(collection: string, id: string): Promise { + return pgRead(this.pg, collection, id); + } + + async update(collection: string, id: string, data: Record): Promise { + return pgUpdate(this.pg, collection, id, data); + } + + async delete(collection: string, id: string): Promise { + return pgDelete(this.pg, collection, id); + } + + async find>( + collection: string, + filter?: Record, + opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' }, + ): Promise { + return pgFind(this.pg, collection, filter, opts); + } + + async findOne>( + collection: string, + filter: Record, + ): Promise { + const results = await this.find(collection, filter, { limit: 1 }); + return results[0] ?? null; + } + + async count(collection: string, filter?: Record): Promise { + return pgCount(this.pg, collection, filter); + } + + async transaction(fn: (tx: StorageAdapter) => Promise): Promise { + return this.pg.transaction(async (tx) => { + const txAdapter = new PgliteTxAdapter(tx as unknown as PgClient); + return fn(txAdapter); + }); + } + + async migrate(): Promise { + for (const name of COLLECTIONS) { + await this.pg.query(` + CREATE TABLE IF NOT EXISTS ${name} ( + id TEXT PRIMARY KEY, + data JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() + ) + `); + } + } + + async close(): Promise { + await this.pg.close(); + } +} + +/** + * Transaction wrapper that delegates to the PGlite transaction connection. + */ +class PgliteTxAdapter implements StorageAdapter { + readonly name = 'pglite'; + private pg: PgClient; + + constructor(pg: PgClient) { + this.pg = pg; + } + + async create>( + collection: string, + data: T, + ): Promise { + return pgCreate(this.pg, collection, data); + } + + async read>(collection: string, id: string): Promise { + return pgRead(this.pg, collection, id); + } + + async update(collection: string, id: string, data: Record): Promise { + return pgUpdate(this.pg, collection, id, data); + } + + async delete(collection: string, id: string): Promise { + return pgDelete(this.pg, collection, id); + } + + async find>( + collection: string, + filter?: Record, + opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' }, + ): Promise { + return pgFind(this.pg, collection, filter, opts); + } + + async findOne>( + collection: string, + filter: Record, + ): Promise { + const results = await this.find(collection, filter, { limit: 1 }); + return results[0] ?? null; + } + + async count(collection: string, filter?: Record): Promise { + return pgCount(this.pg, collection, filter); + } + + async transaction(fn: (tx: StorageAdapter) => Promise): Promise { + // Already inside a transaction — run directly + return fn(this); + } + + async migrate(): Promise { + // No-op inside transaction + } + + async close(): Promise { + // No-op inside transaction + } +} diff --git a/packages/storage/src/adapters/sqlite.ts b/packages/storage/src/adapters/sqlite.ts deleted file mode 100644 index e6752d8..0000000 --- a/packages/storage/src/adapters/sqlite.ts +++ /dev/null @@ -1,283 +0,0 @@ -import Database from 'better-sqlite3'; -import { randomUUID } from 'node:crypto'; -import type { StorageAdapter, StorageConfig } from '../types.js'; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -const COLLECTIONS = [ - 'users', - 'sessions', - 'accounts', - 'projects', - 'missions', - 'tasks', - 'agents', - 'conversations', - 'messages', - 'preferences', - 'insights', - 'skills', - 'events', - 'routing_rules', - 'provider_credentials', - 'agent_logs', - 'teams', - 'team_members', - 'mission_tasks', - 'tickets', - 'summarization_jobs', - 'appreciations', - 'verifications', -] as const; - -function buildFilterClause(filter?: Record): { - clause: string; - params: unknown[]; -} { - if (!filter || Object.keys(filter).length === 0) return { clause: '', params: [] }; - const conditions: string[] = []; - const params: unknown[] = []; - for (const [key, value] of Object.entries(filter)) { - if (key === 'id') { - conditions.push('id = ?'); - params.push(value); - } else { - conditions.push(`json_extract(data_json, '$.${key}') = ?`); - params.push(typeof value === 'object' ? JSON.stringify(value) : value); - } - } - return { clause: ` WHERE ${conditions.join(' AND ')}`, params }; -} - -export class SqliteAdapter implements StorageAdapter { - readonly name = 'sqlite'; - private db: Database.Database; - - constructor(config: Extract) { - this.db = new Database(config.path); - this.db.pragma('journal_mode = WAL'); - this.db.pragma('foreign_keys = ON'); - } - - async create>( - collection: string, - data: T, - ): Promise { - const id = (data as any).id ?? randomUUID(); - const now = new Date().toISOString(); - const rest = Object.fromEntries(Object.entries(data).filter(([k]) => k !== 'id')); - this.db - .prepare( - `INSERT INTO ${collection} (id, data_json, created_at, updated_at) VALUES (?, ?, ?, ?)`, - ) - .run(id, JSON.stringify(rest), now, now); - return { ...data, id } as T & { id: string }; - } - - async read>(collection: string, id: string): Promise { - const row = this.db.prepare(`SELECT * FROM ${collection} WHERE id = ?`).get(id) as any; - if (!row) return null; - return { id: row.id, ...JSON.parse(row.data_json as string) } as T; - } - - async update(collection: string, id: string, data: Record): Promise { - const existing = this.db - .prepare(`SELECT data_json FROM ${collection} WHERE id = ?`) - .get(id) as any; - if (!existing) return false; - const merged = { ...JSON.parse(existing.data_json as string), ...data }; - const now = new Date().toISOString(); - const result = this.db - .prepare(`UPDATE ${collection} SET data_json = ?, updated_at = ? WHERE id = ?`) - .run(JSON.stringify(merged), now, id); - return result.changes > 0; - } - - async delete(collection: string, id: string): Promise { - const result = this.db.prepare(`DELETE FROM ${collection} WHERE id = ?`).run(id); - return result.changes > 0; - } - - async find>( - collection: string, - filter?: Record, - opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' }, - ): Promise { - const { clause, params } = buildFilterClause(filter); - let query = `SELECT * FROM ${collection}${clause}`; - if (opts?.orderBy) { - const dir = opts.order === 'desc' ? 'DESC' : 'ASC'; - const col = - opts.orderBy === 'id' || opts.orderBy === 'created_at' || opts.orderBy === 'updated_at' - ? opts.orderBy - : `json_extract(data_json, '$.${opts.orderBy}')`; - query += ` ORDER BY ${col} ${dir}`; - } - if (opts?.limit) { - query += ` LIMIT ?`; - params.push(opts.limit); - } - if (opts?.offset) { - query += ` OFFSET ?`; - params.push(opts.offset); - } - const rows = this.db.prepare(query).all(...params) as any[]; - return rows.map((row) => ({ id: row.id, ...JSON.parse(row.data_json as string) }) as T); - } - - async findOne>( - collection: string, - filter: Record, - ): Promise { - const results = await this.find(collection, filter, { limit: 1 }); - return results[0] ?? null; - } - - async count(collection: string, filter?: Record): Promise { - const { clause, params } = buildFilterClause(filter); - const row = this.db - .prepare(`SELECT COUNT(*) as count FROM ${collection}${clause}`) - .get(...params) as any; - return row?.count ?? 0; - } - - async transaction(fn: (tx: StorageAdapter) => Promise): Promise { - const txAdapter = new SqliteTxAdapter(this.db); - this.db.exec('BEGIN'); - try { - const result = await fn(txAdapter); - this.db.exec('COMMIT'); - return result; - } catch (err) { - this.db.exec('ROLLBACK'); - throw err; - } - } - - async migrate(): Promise { - const createTable = (name: string) => - this.db.exec(` - CREATE TABLE IF NOT EXISTS ${name} ( - id TEXT PRIMARY KEY, - data_json TEXT NOT NULL DEFAULT '{}', - created_at TEXT NOT NULL DEFAULT (datetime('now')), - updated_at TEXT NOT NULL DEFAULT (datetime('now')) - ) - `); - for (const collection of COLLECTIONS) { - createTable(collection); - } - } - - async close(): Promise { - this.db.close(); - } -} - -/** - * Transaction wrapper that uses the same db handle — better-sqlite3 transactions - * are connection-level, so all statements on the same Database instance within - * a db.transaction() callback participate in the transaction. - */ -class SqliteTxAdapter implements StorageAdapter { - readonly name = 'sqlite'; - private db: Database.Database; - - constructor(db: Database.Database) { - this.db = db; - } - - async create>( - collection: string, - data: T, - ): Promise { - const id = (data as any).id ?? randomUUID(); - const now = new Date().toISOString(); - const rest = Object.fromEntries(Object.entries(data).filter(([k]) => k !== 'id')); - this.db - .prepare( - `INSERT INTO ${collection} (id, data_json, created_at, updated_at) VALUES (?, ?, ?, ?)`, - ) - .run(id, JSON.stringify(rest), now, now); - return { ...data, id } as T & { id: string }; - } - - async read>(collection: string, id: string): Promise { - const row = this.db.prepare(`SELECT * FROM ${collection} WHERE id = ?`).get(id) as any; - if (!row) return null; - return { id: row.id, ...JSON.parse(row.data_json as string) } as T; - } - - async update(collection: string, id: string, data: Record): Promise { - const existing = this.db - .prepare(`SELECT data_json FROM ${collection} WHERE id = ?`) - .get(id) as any; - if (!existing) return false; - const merged = { ...JSON.parse(existing.data_json as string), ...data }; - const now = new Date().toISOString(); - const result = this.db - .prepare(`UPDATE ${collection} SET data_json = ?, updated_at = ? WHERE id = ?`) - .run(JSON.stringify(merged), now, id); - return result.changes > 0; - } - - async delete(collection: string, id: string): Promise { - const result = this.db.prepare(`DELETE FROM ${collection} WHERE id = ?`).run(id); - return result.changes > 0; - } - - async find>( - collection: string, - filter?: Record, - opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' }, - ): Promise { - const { clause, params } = buildFilterClause(filter); - let query = `SELECT * FROM ${collection}${clause}`; - if (opts?.orderBy) { - const dir = opts.order === 'desc' ? 'DESC' : 'ASC'; - const col = - opts.orderBy === 'id' || opts.orderBy === 'created_at' || opts.orderBy === 'updated_at' - ? opts.orderBy - : `json_extract(data_json, '$.${opts.orderBy}')`; - query += ` ORDER BY ${col} ${dir}`; - } - if (opts?.limit) { - query += ` LIMIT ?`; - params.push(opts.limit); - } - if (opts?.offset) { - query += ` OFFSET ?`; - params.push(opts.offset); - } - const rows = this.db.prepare(query).all(...params) as any[]; - return rows.map((row) => ({ id: row.id, ...JSON.parse(row.data_json as string) }) as T); - } - - async findOne>( - collection: string, - filter: Record, - ): Promise { - const results = await this.find(collection, filter, { limit: 1 }); - return results[0] ?? null; - } - - async count(collection: string, filter?: Record): Promise { - const { clause, params } = buildFilterClause(filter); - const row = this.db - .prepare(`SELECT COUNT(*) as count FROM ${collection}${clause}`) - .get(...params) as any; - return row?.count ?? 0; - } - - async transaction(fn: (tx: StorageAdapter) => Promise): Promise { - return fn(this); - } - - async migrate(): Promise { - // No-op inside transaction - } - - async close(): Promise { - // No-op inside transaction - } -} diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 9c624ea..1a5498f 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -1,17 +1,17 @@ export type { StorageAdapter, StorageConfig } from './types.js'; export { createStorageAdapter, registerStorageAdapter } from './factory.js'; export { PostgresAdapter } from './adapters/postgres.js'; -export { SqliteAdapter } from './adapters/sqlite.js'; +export { PgliteAdapter } from './adapters/pglite.js'; import { registerStorageAdapter } from './factory.js'; import { PostgresAdapter } from './adapters/postgres.js'; -import { SqliteAdapter } from './adapters/sqlite.js'; +import { PgliteAdapter } from './adapters/pglite.js'; import type { StorageConfig } from './types.js'; registerStorageAdapter('postgres', (config: StorageConfig) => { return new PostgresAdapter(config as Extract); }); -registerStorageAdapter('sqlite', (config: StorageConfig) => { - return new SqliteAdapter(config as Extract); +registerStorageAdapter('pglite', (config: StorageConfig) => { + return new PgliteAdapter(config as Extract); }); diff --git a/packages/storage/src/types.ts b/packages/storage/src/types.ts index 75d1d0d..039716a 100644 --- a/packages/storage/src/types.ts +++ b/packages/storage/src/types.ts @@ -39,5 +39,5 @@ export interface StorageAdapter { export type StorageConfig = | { type: 'postgres'; url: string } - | { type: 'sqlite'; path: string } + | { type: 'pglite'; dataDir?: string } | { type: 'files'; dataDir: string; format?: 'json' | 'md' }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7b1f94..a057520 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -606,19 +606,16 @@ importers: packages/storage: dependencies: + '@electric-sql/pglite': + specifier: ^0.2.17 + version: 0.2.17 '@mosaic/db': specifier: workspace:^ version: link:../db '@mosaic/types': specifier: workspace:* version: link:../types - better-sqlite3: - specifier: ^12.8.0 - version: 12.8.0 devDependencies: - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 typescript: specifier: ^5.8.0 version: 5.9.3 @@ -10236,6 +10233,7 @@ snapshots: '@types/better-sqlite3@7.6.13': dependencies: '@types/node': 22.19.15 + optional: true '@types/bunyan@1.8.11': dependencies: @@ -10667,6 +10665,7 @@ snapshots: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 + optional: true bidi-js@1.0.3: dependencies: @@ -10677,12 +10676,14 @@ snapshots: bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 + optional: true bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true body-parser@2.2.2: dependencies: @@ -10744,6 +10745,7 @@ snapshots: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + optional: true bullmq@5.71.0: dependencies: @@ -10806,7 +10808,8 @@ snapshots: dependencies: readdirp: 5.0.0 - chownr@1.1.4: {} + chownr@1.1.4: + optional: true chownr@3.0.0: {} @@ -10965,10 +10968,12 @@ snapshots: decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 + optional: true deep-eql@5.0.2: {} - deep-extend@0.6.0: {} + deep-extend@0.6.0: + optional: true deep-is@0.1.4: {} @@ -11386,7 +11391,8 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - expand-template@2.0.3: {} + expand-template@2.0.3: + optional: true expect-type@1.3.0: {} @@ -11552,7 +11558,8 @@ snapshots: transitivePeerDependencies: - supports-color - file-uri-to-path@1.0.0: {} + file-uri-to-path@1.0.0: + optional: true fill-range@7.1.1: dependencies: @@ -11614,7 +11621,8 @@ snapshots: fresh@2.0.0: {} - fs-constants@1.0.0: {} + fs-constants@1.0.0: + optional: true fsevents@2.3.2: optional: true @@ -11709,7 +11717,8 @@ snapshots: transitivePeerDependencies: - supports-color - github-from-package@0.0.0: {} + github-from-package@0.0.0: + optional: true glob-parent@6.0.2: dependencies: @@ -11895,7 +11904,8 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} + ini@1.3.8: + optional: true ink-spinner@5.0.0(ink@5.2.1(@types/react@18.3.28)(react@18.3.1))(react@18.3.1): dependencies: @@ -12603,7 +12613,8 @@ snapshots: mimic-function@5.0.1: {} - mimic-response@3.1.0: {} + mimic-response@3.1.0: + optional: true minimatch@10.2.4: dependencies: @@ -12617,7 +12628,8 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist@1.2.8: {} + minimist@1.2.8: + optional: true minipass@7.1.3: {} @@ -12625,7 +12637,8 @@ snapshots: dependencies: minipass: 7.1.3 - mkdirp-classic@0.5.3: {} + mkdirp-classic@0.5.3: + optional: true module-details-from-path@1.0.4: {} @@ -12672,7 +12685,8 @@ snapshots: nanostores@1.1.1: {} - napi-build-utils@2.0.0: {} + napi-build-utils@2.0.0: + optional: true natural-compare@1.4.0: {} @@ -12711,6 +12725,7 @@ snapshots: node-abi@3.89.0: dependencies: semver: 7.7.4 + optional: true node-abort-controller@3.1.1: {} @@ -13074,6 +13089,7 @@ snapshots: simple-get: 4.0.1 tar-fs: 2.1.4 tunnel-agent: 0.6.0 + optional: true prelude-ls@1.2.1: {} @@ -13163,6 +13179,7 @@ snapshots: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 + optional: true react-dom@19.2.4(react@19.2.4): dependencies: @@ -13214,6 +13231,7 @@ snapshots: inherits: 2.0.4 string_decoder: 1.1.1 util-deprecate: 1.0.2 + optional: true readdirp@5.0.0: {} @@ -13468,13 +13486,15 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: {} + simple-concat@1.0.1: + optional: true simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 + optional: true sisteransi@1.0.5: {} @@ -13639,7 +13659,8 @@ snapshots: strip-final-newline@3.0.0: {} - strip-json-comments@2.0.1: {} + strip-json-comments@2.0.1: + optional: true strip-json-comments@3.1.1: {} @@ -13684,6 +13705,7 @@ snapshots: mkdirp-classic: 0.5.3 pump: 3.0.4 tar-stream: 2.2.0 + optional: true tar-stream@2.2.0: dependencies: @@ -13692,6 +13714,7 @@ snapshots: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true tar@7.5.13: dependencies: @@ -13808,6 +13831,7 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 + optional: true turbo-darwin-64@2.8.16: optional: true