Skip to content

Commit

Permalink
SQLocal constructor accepts additional options: readOnly and verbose
Browse files Browse the repository at this point in the history
  • Loading branch information
DallasHoff committed Jul 21, 2024
1 parent 10f0b6a commit ee7aa58
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 27 deletions.
16 changes: 16 additions & 0 deletions docs/guide/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ With the client initialized, you are ready to [start making queries](/api/sql).

<!-- @include: ../_partials/initialization-note.md -->

## Options

The `SQLocal` constructor can also be passed an object to accept additional options.

```javascript
export const db = new SQLocal({
databasePath: 'database.sqlite3',
readOnly: true,
verbose: true,
});
```

- **`databasePath`** (`string`) - The file name for the database file. This is the only required option.
- **`readOnly`** (`boolean`) - If `true`, connect to the database in read-only mode. Attempts to run queries that would mutate the database will throw an error.
- **`verbose`** (`boolean`) - If `true`, any SQL executed on the database will be logged to the console.

## Vite Configuration

Vite currently has an issue that prevents it from loading web worker files correctly with the default configuration. If you use Vite, please add the below to your [Vite configuration](https://vitejs.dev/config/) to fix this. Don't worry: it will have no impact on production performance.
Expand Down
12 changes: 8 additions & 4 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
GetInfoMessage,
Statement,
DatabaseInfo,
ClientConfig,
} from './types.js';
import { sqlTag } from './lib/sql-tag.js';
import { convertRowsToObjects } from './lib/convert-rows-to-objects.js';
Expand All @@ -36,18 +37,21 @@ export class SQLocal {
]
>();

constructor(databasePath: string) {
constructor(databasePath: string);
constructor(config: ClientConfig);
constructor(config: string | ClientConfig) {
config = typeof config === 'string' ? { databasePath: config } : config;

this.worker = new Worker(new URL('./worker', import.meta.url), {
type: 'module',
});
this.worker.addEventListener('message', this.processMessageEvent);

this.proxy = coincident(this.worker) as WorkerProxy;
this.databasePath = databasePath;
this.databasePath = config.databasePath;
this.worker.postMessage({
type: 'config',
key: 'databasePath',
value: databasePath,
config,
} satisfies ConfigMessage);
}

Expand Down
29 changes: 14 additions & 15 deletions src/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
RawResultData,
GetInfoMessage,
Sqlite3StorageType,
ConfigMessage,
} from './types.js';

export class SQLocalProcessor {
Expand All @@ -38,6 +39,12 @@ export class SQLocalProcessor {
protected init = async (): Promise<void> => {
if (!this.config.databasePath) return;

const { databasePath, readOnly, verbose } = this.config;
const flags = [
readOnly === true ? 'r' : 'cw',
verbose === true ? 't' : '',
].join('');

try {
if (!this.sqlite3) {
this.sqlite3 = await sqlite3InitModule();
Expand All @@ -48,13 +55,13 @@ export class SQLocalProcessor {
}

if ('opfs' in this.sqlite3) {
this.db = new this.sqlite3.oo1.OpfsDb(this.config.databasePath, 'cw');
this.db = new this.sqlite3.oo1.OpfsDb(databasePath, flags);
this.dbStorageType = 'opfs';
} else {
this.db = new this.sqlite3.oo1.DB(this.config.databasePath, 'cw');
this.db = new this.sqlite3.oo1.DB(databasePath, flags);
this.dbStorageType = 'memory';
console.warn(
`The origin private file system is not available, so ${this.config.databasePath} will not be persisted. Make sure your web server is configured to use the correct HTTP response headers (See https://sqlocal.dallashoffman.com/guide/setup#cross-origin-isolation).`
`The origin private file system is not available, so ${databasePath} will not be persisted. Make sure your web server is configured to use the correct HTTP response headers (See https://sqlocal.dallashoffman.com/guide/setup#cross-origin-isolation).`
);
}
} catch (error) {
Expand Down Expand Up @@ -84,7 +91,7 @@ export class SQLocalProcessor {

switch (message.type) {
case 'config':
this.editConfig(message.key, message.value);
this.editConfig(message);
break;
case 'query':
case 'batch':
Expand All @@ -111,17 +118,9 @@ export class SQLocalProcessor {
}
};

protected editConfig = <T extends keyof ProcessorConfig>(
key: T,
value: ProcessorConfig[T]
): void => {
if (this.config[key] === value) return;

this.config[key] = value;

if (key === 'databasePath') {
this.init();
}
protected editConfig = (message: ConfigMessage) => {
this.config = message.config;
this.init();
};

protected exec = (message: QueryMessage | BatchMessage): void => {
Expand Down
11 changes: 7 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ export type RawResultData = {

// Database status

export type ProcessorConfig = {
databasePath?: string;
export type ClientConfig = {
databasePath: string;
readOnly?: boolean;
verbose?: boolean;
};

export type ProcessorConfig = Partial<ClientConfig>;

export type DatabaseInfo = {
databasePath?: string;
databaseSizeBytes?: number;
Expand Down Expand Up @@ -79,8 +83,7 @@ export type FunctionMessage = {
};
export type ConfigMessage = {
type: 'config';
key: keyof ProcessorConfig;
value: any;
config: ProcessorConfig;
};
export type ImportMessage = {
type: 'import';
Expand Down
54 changes: 54 additions & 0 deletions test/init.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { afterAll, afterEach, beforeEach, describe, expect, it } from 'vitest';
import { SQLocal } from '../src/index';

describe('init', () => {
const databasePath = 'init-test.sqlite3';
const { sql } = new SQLocal({ databasePath });

beforeEach(async () => {
await sql`CREATE TABLE nums (num INTEGER NOT NULL)`;
await sql`INSERT INTO nums (num) VALUES (0)`;
});

afterEach(async () => {
await sql`DROP TABLE nums`;
});

afterAll(async () => {
const opfs = await navigator.storage.getDirectory();
await opfs.removeEntry(databasePath);
});

it('should be cross-origin isolated', () => {
expect(crossOriginIsolated).toBe(true);
});

it('should create a file in the OPFS', async () => {
const opfs = await navigator.storage.getDirectory();
const fileHandle = await opfs.getFileHandle(databasePath);
const file = await fileHandle.getFile();
expect(file.size).toBeGreaterThan(0);
});

it('should enable read-only mode', async () => {
const { sql, destroy } = new SQLocal({
databasePath,
readOnly: true,
});

const write = async () => {
await sql`INSERT INTO nums (num) VALUES (1)`;
};
expect(write).rejects.toThrowError(
'SQLITE_IOERR_WRITE: sqlite3 result code 778: disk I/O error'
);

const read = async () => {
return await sql`SELECT * FROM nums`;
};
const data = await read();
expect(data).toEqual([{ num: 0 }]);

await destroy();
});
});
4 changes: 0 additions & 4 deletions test/sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,4 @@ describe('sql', () => {
const select3 = await sql(sqlStr, 1);
expect(select3).toEqual([{ name: 'bread' }]);
});

it('should be cross-origin isolated', () => {
expect(crossOriginIsolated).toBe(true);
});
});

0 comments on commit ee7aa58

Please sign in to comment.