diff --git a/src/drivers/db0.ts b/src/drivers/db0.ts index ec1f0717..dea09141 100644 --- a/src/drivers/db0.ts +++ b/src/drivers/db0.ts @@ -25,7 +25,7 @@ const DEFAULT_TABLE_NAME = "unstorage"; const kExperimentalWarning = "__unstorage_db0_experimental_warning__"; const driver: DriverFactory>> = (opts) => { - opts.tableName = opts.tableName || DEFAULT_TABLE_NAME; + const tableName = opts.tableName || DEFAULT_TABLE_NAME; let setupPromise: Promise | undefined; let setupDone = false; @@ -40,7 +40,7 @@ const driver: DriverFactory>> = (o ); (globalThis as any)[kExperimentalWarning] = true; } - setupPromise = setupTable(opts).then(() => { + setupPromise = setupTable(opts.database, tableName).then(() => { setupDone = true; setupPromise = undefined; }); @@ -52,43 +52,43 @@ const driver: DriverFactory>> = (o return { name: DRIVER_NAME, - options: opts, + options: { ...opts, tableName }, getInstance: () => opts.database, async hasItem(key) { await ensureTable(); const { rows } = isMysql ? await opts.database.sql - /* sql */ `SELECT EXISTS (SELECT 1 FROM {${opts.tableName}} WHERE \`key\` = ${key}) AS \`value\`` + /* sql */ `SELECT EXISTS (SELECT 1 FROM {${tableName}} WHERE \`key\` = ${key}) AS \`value\`` : await opts.database.sql - /* sql */ `SELECT EXISTS (SELECT 1 FROM {${opts.tableName}} WHERE key = ${key}) AS value`; + /* sql */ `SELECT EXISTS (SELECT 1 FROM {${tableName}} WHERE key = ${key}) AS value`; return rows?.[0]?.value == "1"; }, getItem: async (key) => { await ensureTable(); const { rows } = isMysql ? await opts.database.sql - /* sql */ `SELECT value FROM {${opts.tableName}} WHERE \`key\` = ${key}` + /* sql */ `SELECT value FROM {${tableName}} WHERE \`key\` = ${key}` : await opts.database.sql - /* sql */ `SELECT value FROM {${opts.tableName}} WHERE key = ${key}`; + /* sql */ `SELECT value FROM {${tableName}} WHERE key = ${key}`; return rows?.[0]?.value ?? null; }, getItemRaw: async (key) => { await ensureTable(); const { rows } = isMysql ? await opts.database.sql - /* sql */ `SELECT \`blob\` as value FROM {${opts.tableName}} WHERE \`key\` = ${key}` + /* sql */ `SELECT \`blob\` as value FROM {${tableName}} WHERE \`key\` = ${key}` : await opts.database.sql - /* sql */ `SELECT blob as value FROM {${opts.tableName}} WHERE key = ${key}`; + /* sql */ `SELECT blob as value FROM {${tableName}} WHERE key = ${key}`; return rows?.[0]?.value ?? null; }, setItem: async (key, value) => { await ensureTable(); if (isMysql) { await opts.database.sql - /* sql */ `INSERT INTO {${opts.tableName}} (\`key\`, \`value\`, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE value = ${value}, updated_at = CURRENT_TIMESTAMP`; + /* sql */ `INSERT INTO {${tableName}} (\`key\`, \`value\`, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE value = ${value}, updated_at = CURRENT_TIMESTAMP`; } else { await opts.database.sql - /* sql */ `INSERT INTO {${opts.tableName}} (key, value, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = ${value}, updated_at = CURRENT_TIMESTAMP`; + /* sql */ `INSERT INTO {${tableName}} (key, value, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = ${value}, updated_at = CURRENT_TIMESTAMP`; } }, async setItemRaw(key, value) { @@ -96,27 +96,27 @@ const driver: DriverFactory>> = (o if (isMysql) { const blob = Buffer.from(value) as any; await opts.database.sql - /* sql */ `INSERT INTO {${opts.tableName}} (\`key\`, \`blob\`, created_at, updated_at) VALUES (${key}, ${blob}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE \`blob\` = ${blob}, updated_at = CURRENT_TIMESTAMP`; + /* sql */ `INSERT INTO {${tableName}} (\`key\`, \`blob\`, created_at, updated_at) VALUES (${key}, ${blob}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE \`blob\` = ${blob}, updated_at = CURRENT_TIMESTAMP`; } else { await opts.database.sql - /* sql */ `INSERT INTO {${opts.tableName}} (key, blob, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET blob = ${value}, updated_at = CURRENT_TIMESTAMP`; + /* sql */ `INSERT INTO {${tableName}} (key, blob, created_at, updated_at) VALUES (${key}, ${value}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET blob = ${value}, updated_at = CURRENT_TIMESTAMP`; } }, removeItem: async (key) => { await ensureTable(); if (isMysql) { - await opts.database.sql /* sql */ `DELETE FROM {${opts.tableName}} WHERE \`key\`=${key}`; + await opts.database.sql /* sql */ `DELETE FROM {${tableName}} WHERE \`key\`=${key}`; } else { - await opts.database.sql /* sql */ `DELETE FROM {${opts.tableName}} WHERE key=${key}`; + await opts.database.sql /* sql */ `DELETE FROM {${tableName}} WHERE key=${key}`; } }, getMeta: async (key) => { await ensureTable(); const { rows } = isMysql ? await opts.database.sql - /* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE \`key\` = ${key}` + /* sql */ `SELECT created_at, updated_at FROM {${tableName}} WHERE \`key\` = ${key}` : await opts.database.sql - /* sql */ `SELECT created_at, updated_at FROM {${opts.tableName}} WHERE key = ${key}`; + /* sql */ `SELECT created_at, updated_at FROM {${tableName}} WHERE key = ${key}`; return { birthtime: toDate(rows?.[0]?.created_at), @@ -127,15 +127,15 @@ const driver: DriverFactory>> = (o await ensureTable(); const { rows } = isMysql ? await opts.database.sql - /* sql */ `SELECT \`key\` FROM {${opts.tableName}} WHERE \`key\` LIKE ${base + "%"}` + /* sql */ `SELECT \`key\` FROM {${tableName}} WHERE \`key\` LIKE ${base + "%"}` : await opts.database.sql - /* sql */ `SELECT key FROM {${opts.tableName}} WHERE key LIKE ${base + "%"}`; + /* sql */ `SELECT key FROM {${tableName}} WHERE key LIKE ${base + "%"}`; return rows?.map((r) => r.key); }, clear: async () => { await ensureTable(); - await opts.database.sql /* sql */ `DELETE FROM {${opts.tableName}}`; + await opts.database.sql /* sql */ `DELETE FROM {${tableName}}`; }, dispose: async () => { await opts.database.dispose(); @@ -144,12 +144,12 @@ const driver: DriverFactory>> = (o }; /** Run database init/migration once */ -async function setupTable(opts: DB0DriverOptions) { - switch (opts.database.dialect) { +async function setupTable(database: Database, tableName: string) { + switch (database.dialect) { case "sqlite": case "libsql": { - await opts.database.sql /* sql */ ` - CREATE TABLE IF NOT EXISTS {${opts.tableName}} ( + await database.sql /* sql */ ` + CREATE TABLE IF NOT EXISTS {${tableName}} ( key TEXT PRIMARY KEY, value TEXT, blob BLOB, @@ -160,8 +160,8 @@ async function setupTable(opts: DB0DriverOptions) { return; } case "postgresql": { - await opts.database.sql /* sql */ ` - CREATE TABLE IF NOT EXISTS {${opts.tableName}} ( + await database.sql /* sql */ ` + CREATE TABLE IF NOT EXISTS {${tableName}} ( key VARCHAR(255) NOT NULL PRIMARY KEY, value TEXT, blob BYTEA, @@ -172,8 +172,8 @@ async function setupTable(opts: DB0DriverOptions) { return; } case "mysql": { - await opts.database.sql /* sql */ ` - CREATE TABLE IF NOT EXISTS {${opts.tableName}} ( + await database.sql /* sql */ ` + CREATE TABLE IF NOT EXISTS {${tableName}} ( \`key\` VARCHAR(255) NOT NULL PRIMARY KEY, \`value\` LONGTEXT, \`blob\` BLOB, @@ -184,7 +184,7 @@ async function setupTable(opts: DB0DriverOptions) { return; } default: { - throw createError(DRIVER_NAME, `unsuppoted SQL dialect: ${opts.database.dialect}`); + throw createError(DRIVER_NAME, `unsuppoted SQL dialect: ${database.dialect}`); } } } diff --git a/src/drivers/fs-lite.ts b/src/drivers/fs-lite.ts index 79a6cdc8..68ef245c 100644 --- a/src/drivers/fs-lite.ts +++ b/src/drivers/fs-lite.ts @@ -19,7 +19,7 @@ const driver: DriverFactory = (opts = {}) => { throw createRequiredError(DRIVER_NAME, "base"); } - opts.base = resolve(opts.base); + const base = resolve(opts.base); const r = (key: string) => { if (PATH_TRAVERSE_RE.test(key)) { throw createError( @@ -27,13 +27,13 @@ const driver: DriverFactory = (opts = {}) => { `Invalid key: ${JSON.stringify(key)}. It should not contain .. segments`, ); } - const resolved = join(opts.base!, key.replace(/:/g, "/")); + const resolved = join(base, key.replace(/:/g, "/")); return resolved; }; return { name: DRIVER_NAME, - options: opts, + options: { ...opts, base }, flags: { maxDepth: true, }, diff --git a/src/drivers/planetscale.ts b/src/drivers/planetscale.ts index 715f2606..90f5760d 100644 --- a/src/drivers/planetscale.ts +++ b/src/drivers/planetscale.ts @@ -31,9 +31,10 @@ export const DRIVER_DEPENDENCIES: DriverDependencies = { }; const DRIVER_NAME = "planetscale"; +const DEFAULT_TABLE_NAME = "storage"; const driver: DriverFactory> = (opts = {}) => { - opts.table = opts.table || "storage"; + const table = opts.table || DEFAULT_TABLE_NAME; let _connection: Promise | undefined; const getConnection = () => @@ -63,18 +64,18 @@ const driver: DriverFactory> = (op return { name: DRIVER_NAME, - options: opts, + options: { ...opts, table }, getInstance: getConnection, hasItem: async (key) => { const res = await ( await getConnection() - ).execute(`SELECT EXISTS (SELECT 1 FROM ${opts.table} WHERE id = :key) as value;`, { key }); + ).execute(`SELECT EXISTS (SELECT 1 FROM ${table} WHERE id = :key) as value;`, { key }); return rows<{ value: string }[]>(res)[0]?.value == "1"; }, getItem: async (key) => { const res = await ( await getConnection() - ).execute(`SELECT value from ${opts.table} WHERE id=:key;`, { + ).execute(`SELECT value from ${table} WHERE id=:key;`, { key, }); return rows(res)[0]?.value ?? null; @@ -83,17 +84,17 @@ const driver: DriverFactory> = (op await ( await getConnection() ).execute( - `INSERT INTO ${opts.table} (id, value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE value = :value;`, + `INSERT INTO ${table} (id, value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE value = :value;`, { key, value }, ); }, removeItem: async (key) => { - await (await getConnection()).execute(`DELETE FROM ${opts.table} WHERE id=:key;`, { key }); + await (await getConnection()).execute(`DELETE FROM ${table} WHERE id=:key;`, { key }); }, getMeta: async (key) => { const res = await ( await getConnection() - ).execute(`SELECT created_at, updated_at from ${opts.table} WHERE id=:key;`, { key }); + ).execute(`SELECT created_at, updated_at from ${table} WHERE id=:key;`, { key }); return { birthtime: rows(res)[0]?.created_at, mtime: rows(res)[0]?.updated_at, @@ -102,11 +103,11 @@ const driver: DriverFactory> = (op getKeys: async (base = "") => { const res = await ( await getConnection() - ).execute(`SELECT id from ${opts.table} WHERE id LIKE :base;`, { base: `${base}%` }); + ).execute(`SELECT id from ${table} WHERE id LIKE :base;`, { base: `${base}%` }); return rows(res).map((r) => r.id); }, clear: async () => { - await (await getConnection()).execute(`DELETE FROM ${opts.table};`); + await (await getConnection()).execute(`DELETE FROM ${table};`); }, }; }; diff --git a/test/drivers/db0.test.ts b/test/drivers/db0.test.ts index cb53e232..05b70ddb 100644 --- a/test/drivers/db0.test.ts +++ b/test/drivers/db0.test.ts @@ -54,6 +54,12 @@ for (const driver of drivers) { testDriver({ driver: () => db0Driver({ database: db }), additionalTests: (ctx) => { + it("does not mutate input options", () => { + const opts = { database: db }; + const instance = db0Driver(opts); + expect(opts).toEqual({ database: db }); + expect(instance.options?.tableName).toBe("unstorage"); + }); it("meta", async () => { await ctx.storage.setItem("meta:test", "test_data"); diff --git a/test/drivers/fs-lite.test.ts b/test/drivers/fs-lite.test.ts index e5e5b3b7..6e25cfb8 100644 --- a/test/drivers/fs-lite.test.ts +++ b/test/drivers/fs-lite.test.ts @@ -10,6 +10,12 @@ describe("drivers: fs-lite", () => { testDriver({ driver: driver({ base: dir }), additionalTests(ctx) { + it("does not mutate input options", () => { + const opts = { base: "./tmp/fs-lite-opts" }; + const instance = driver(opts); + expect(opts).toEqual({ base: "./tmp/fs-lite-opts" }); + expect(instance.options?.base).toBe(resolve("./tmp/fs-lite-opts")); + }); it("check filesystem", async () => { await ctx.storage.setItem("s1:a", "test_data"); expect(await readFile(resolve(dir, "s1/a"), "utf8")).toBe("test_data"); diff --git a/test/drivers/planetscale.test.ts b/test/drivers/planetscale.test.ts new file mode 100644 index 00000000..d3f2dc10 --- /dev/null +++ b/test/drivers/planetscale.test.ts @@ -0,0 +1,11 @@ +import { describe, it, expect } from "vitest"; +import driver from "../../src/drivers/planetscale.ts"; + +describe("drivers: planetscale", () => { + it("does not mutate input options", () => { + const opts = { url: "mysql://user:pass@host/db" }; + const instance = driver(opts); + expect(opts).toEqual({ url: "mysql://user:pass@host/db" }); + expect(instance.options?.table).toBe("storage"); + }); +});