@deterministic-code/datasources-sqlite 0.1.0 → 0.1.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.
Files changed (41) hide show
  1. package/dist/SqliteCreateTableStatementRenderer.d.ts +6 -0
  2. package/dist/SqliteDriver.d.ts +12 -0
  3. package/dist/SqliteStatementRenderer.d.ts +6 -0
  4. package/dist/columnTypes.d.ts +1 -0
  5. package/dist/command.d.ts +13 -0
  6. package/dist/connection-options.d.ts +6 -0
  7. package/dist/connection.d.ts +20 -0
  8. package/dist/converters/BinaryConverter.d.ts +5 -0
  9. package/dist/converters/BoolConverter.d.ts +5 -0
  10. package/dist/converters/DateTimeConverter.d.ts +5 -0
  11. package/dist/converters/DecimalConverter.d.ts +5 -0
  12. package/dist/converters/SqliteRowTypeConverters.d.ts +3 -0
  13. package/dist/converters/UuidConverter.d.ts +5 -0
  14. package/dist/i-sqlite-driver.d.ts +17 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/sqlitePath.d.ts +5 -0
  17. package/dist/transaction.d.ts +9 -0
  18. package/package.json +10 -6
  19. package/src/SqliteCreateTableStatementRenderer.ts +1 -1
  20. package/src/SqliteDriver.ts +1 -1
  21. package/src/SqliteStatementRenderer.test.ts +60 -0
  22. package/src/SqliteStatementRenderer.ts +1 -1
  23. package/src/command.ts +1 -1
  24. package/src/connection.ts +1 -1
  25. package/src/converters/BinaryConverter.test.ts +11 -0
  26. package/src/converters/BinaryConverter.ts +1 -1
  27. package/src/converters/BoolConverter.test.ts +14 -0
  28. package/src/converters/BoolConverter.ts +1 -1
  29. package/src/converters/DateTimeConverter.test.ts +13 -0
  30. package/src/converters/DateTimeConverter.ts +1 -1
  31. package/src/converters/DecimalConverter.test.ts +11 -0
  32. package/src/converters/DecimalConverter.ts +1 -1
  33. package/src/converters/SqliteRowTypeConverters.test.ts +18 -0
  34. package/src/converters/SqliteRowTypeConverters.ts +1 -1
  35. package/src/converters/UuidConverter.test.ts +11 -0
  36. package/src/converters/UuidConverter.ts +1 -1
  37. package/src/createFromDriver.test.ts +15 -0
  38. package/src/expected-repository-console.test.ts +95 -0
  39. package/src/i-sqlite-driver.ts +1 -1
  40. package/src/sqlite.test.ts +296 -0
  41. package/src/transaction.ts +1 -1
@@ -0,0 +1,6 @@
1
+ import { CreateTableStatement, CreateTableStatementTextRenderer, type Column } from "@datasources/core";
2
+ export declare class SqliteCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
3
+ render(statement: CreateTableStatement): string;
4
+ protected typeName(column: Column): string;
5
+ protected identity(column: Column): string;
6
+ }
@@ -0,0 +1,12 @@
1
+ import { StatementTextRenderer, type Cell, type DatasourceOptions, type ICommand, type IConnection, type IDriver, type Information, type ITransaction, type IsolationLevel, type TypeConverter } from "@datasources/core";
2
+ import { columnTypes } from "./columnTypes.js";
3
+ export { columnTypes };
4
+ export declare class SqliteDriver implements IDriver {
5
+ readonly statementRenderer: StatementTextRenderer;
6
+ readonly information: Information;
7
+ rowTypeConverters: Record<string, TypeConverter<Cell, Cell>>;
8
+ columnType: (name: string) => string;
9
+ connection: (options?: DatasourceOptions) => IConnection;
10
+ command: (connection: IConnection) => ICommand;
11
+ transaction: (connection: IConnection, isolation?: IsolationLevel) => ITransaction;
12
+ }
@@ -0,0 +1,6 @@
1
+ import { CreateTableStatement, SelectInsertedIdStatement, StatementRenderer, type StatementTextRenderer } from "@datasources/core";
2
+ export declare class SqliteStatementRenderer extends StatementRenderer {
3
+ constructor(text: StatementTextRenderer);
4
+ protected visitSelectInsertedId(inserted: SelectInsertedIdStatement): string;
5
+ protected visitCreateTable(create: CreateTableStatement): string;
6
+ }
@@ -0,0 +1 @@
1
+ export declare const columnTypes: Record<string, string>;
@@ -0,0 +1,13 @@
1
+ import { CommandMiddlewareHost, Native, StatementTextRenderer, Response, type Cells, type ICommand, type IReader, type IStatement, type Parameter } from "@datasources/core";
2
+ import type { SqliteConnection } from "./connection.js";
3
+ export declare class SqliteCommand extends CommandMiddlewareHost implements ICommand {
4
+ readonly connection: SqliteConnection;
5
+ private readonly statements;
6
+ constructor(connection: SqliteConnection, statements?: StatementTextRenderer);
7
+ executeReaderNative(text: Native, parameters?: Parameter[]): Promise<IReader<Cells>>;
8
+ executeReaderStatement(statement: IStatement, parameters?: Parameter[]): Promise<IReader<Cells>>;
9
+ executeStatement(statement: IStatement, parameters?: Parameter[]): Promise<Response>;
10
+ private firstCell;
11
+ executeNative(text: Native, parameters?: Parameter[]): Promise<Response>;
12
+ private iterate;
13
+ }
@@ -0,0 +1,6 @@
1
+ import type { SqliteDatabaseCtor } from "./i-sqlite-driver.js";
2
+ export interface SqliteConnectionOptions {
3
+ filename?: string;
4
+ url?: string;
5
+ Database?: SqliteDatabaseCtor;
6
+ }
@@ -0,0 +1,20 @@
1
+ import { type DatasourceOptions, type ICommand, type IConnection, type IsolationLevelName, type ITransaction } from "@datasources/core";
2
+ import type { SqliteConnectionOptions } from "./connection-options.js";
3
+ import type { SqliteDatabase } from "./i-sqlite-driver.js";
4
+ export declare class SqliteConnection implements IConnection {
5
+ readonly provider = "sqlite";
6
+ filename: string;
7
+ isOpen: boolean;
8
+ private db?;
9
+ private readonly Database?;
10
+ static from: (options?: DatasourceOptions) => SqliteConnection;
11
+ constructor(options?: SqliteConnectionOptions);
12
+ createCommand: () => ICommand;
13
+ open(): Promise<void>;
14
+ close(): Promise<void>;
15
+ [Symbol.asyncDispose]: () => Promise<void>;
16
+ startTransaction(isolation?: IsolationLevelName): Promise<ITransaction>;
17
+ ensureOpen(): void;
18
+ require(): SqliteDatabase;
19
+ exec(sql: string): void;
20
+ }
@@ -0,0 +1,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class BinaryConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class BoolConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class DateTimeConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class DecimalConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -0,0 +1,3 @@
1
+ import { type Cell, type TypeConverter } from "@datasources/core";
2
+ export declare const SqliteRowTypeConverters: Record<string, TypeConverter<Cell, Cell>>;
3
+ export declare const reverse: (value: Cell) => Cell;
@@ -0,0 +1,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class UuidConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -0,0 +1,17 @@
1
+ import type { Cell, Cells, Scalar } from "@datasources/core";
2
+ export interface SqliteStatement {
3
+ run(params?: Record<string, Scalar>): {
4
+ changes: number;
5
+ lastInsertRowid?: Cell;
6
+ };
7
+ all(params?: Record<string, Scalar>): Cells[];
8
+ iterate(params?: Record<string, Scalar>): IterableIterator<Cells>;
9
+ }
10
+ export interface SqliteDatabase {
11
+ prepare(sql: string): SqliteStatement;
12
+ exec(sql: string): void;
13
+ close(): void;
14
+ }
15
+ export type SqliteDatabaseCtor = new (filename: string, options?: {
16
+ timeout?: number;
17
+ }) => SqliteDatabase;
@@ -0,0 +1,2 @@
1
+ export { SqliteDriver } from "./SqliteDriver.js";
2
+ export { SqliteStatementRenderer } from "./SqliteStatementRenderer.js";
@@ -0,0 +1,5 @@
1
+ export declare const sqliteFilename: (url: string) => string | undefined;
2
+ export declare const filenameOf: (options: {
3
+ filename?: string;
4
+ url?: string;
5
+ }) => string;
@@ -0,0 +1,9 @@
1
+ import { type ITransaction, type IsolationLevelName } from "@datasources/core";
2
+ import type { SqliteConnection } from "./connection.js";
3
+ export declare class SqliteTransaction implements ITransaction {
4
+ readonly connection: SqliteConnection;
5
+ readonly isolation: IsolationLevelName;
6
+ constructor(connection: SqliteConnection, isolation?: IsolationLevelName);
7
+ commit(): Promise<void>;
8
+ abort(): Promise<void>;
9
+ }
package/package.json CHANGED
@@ -1,22 +1,26 @@
1
1
  {
2
2
  "name": "@deterministic-code/datasources-sqlite",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
- "types": "./src/index.ts",
6
+ "types": "./dist/index.d.ts",
7
7
  "exports": {
8
- ".": "./src/index.ts"
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./src/index.ts",
11
+ "default": "./src/index.ts"
12
+ }
9
13
  },
10
14
  "files": [
15
+ "dist",
11
16
  "src"
12
17
  ],
13
18
  "publishConfig": {
14
19
  "access": "public"
15
20
  },
16
- "license": "MIT",
17
21
  "dependencies": {
18
- "@deterministic-code/datasources": "^0.1.0",
19
22
  "@types/better-sqlite3": "^7.6.13",
20
- "better-sqlite3": "^12.10.0"
23
+ "better-sqlite3": "^12.10.0",
24
+ "@deterministic-code/datasources": "^0.1.1"
21
25
  }
22
26
  }
@@ -1,4 +1,4 @@
1
- import { CreateTableStatement, CreateTableStatementTextRenderer, PrimaryKey, type Column } from "@deterministic-code/datasources";
1
+ import { CreateTableStatement, CreateTableStatementTextRenderer, PrimaryKey, type Column } from "@datasources/core";
2
2
  import { columnTypes } from "./columnTypes.ts";
3
3
 
4
4
  export class SqliteCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
@@ -10,7 +10,7 @@ import {
10
10
  type ITransaction,
11
11
  type IsolationLevel,
12
12
  type TypeConverter,
13
- } from "@deterministic-code/datasources";
13
+ } from "@datasources/core";
14
14
  import { SqliteConnection } from "./connection.ts";
15
15
  import { SqliteCommand } from "./command.ts";
16
16
  import { SqliteTransaction } from "./transaction.ts";
@@ -0,0 +1,60 @@
1
+ import {
2
+ ColumnDefault,
3
+ CreateTableStatement,
4
+ DefaultStatementTextRenderer,
5
+ ForeignKey,
6
+ SelectInsertedIdStatement,
7
+ NotNull,
8
+ PrimaryKey,
9
+ ReferentialAction,
10
+ SelectStatement,
11
+ StatementTextRenderer,
12
+ } from "@datasources/core";
13
+ import { describe, expect, it } from "vitest";
14
+ import { SqliteCreateTableStatementRenderer } from "./SqliteCreateTableStatementRenderer.ts";
15
+ import { SqliteStatementRenderer } from "./SqliteStatementRenderer.ts";
16
+
17
+ describe("SqliteStatementRenderer", () => {
18
+ const renderer = new StatementTextRenderer(t => new SqliteStatementRenderer(t));
19
+
20
+ it("renders inserted id and create table", () => {
21
+ expect(renderer.render(new SelectInsertedIdStatement("users", "id"))).toBe(
22
+ "SELECT last_insert_rowid() AS id",
23
+ );
24
+
25
+ const create = new CreateTableStatement(
26
+ "orders",
27
+ [
28
+ { name: "id", type: "INTEGER", default: new ColumnDefault("0", "df_id"), notNull: new NotNull("nn_id") },
29
+ { name: "user_id", type: "INTEGER" },
30
+ ],
31
+ [
32
+ new PrimaryKey("pk_orders", ["id"]),
33
+ new ForeignKey("fk_orders_user", ["user_id"], "users", ["id"], ReferentialAction.Cascade),
34
+ ],
35
+ );
36
+ expect(renderer.render(create)).toBe(new SqliteCreateTableStatementRenderer().render(create));
37
+ expect(renderer.render(new SelectStatement({ table: "users", fields: ["id"] }))).toBe(
38
+ "SELECT id FROM users",
39
+ );
40
+ });
41
+
42
+ it("renders canonical types", () => {
43
+ const create = new CreateTableStatement("users", [
44
+ { name: "id", type: "int", default: new ColumnDefault("0"), notNull: new NotNull(), identity: true },
45
+ ]);
46
+ expect(renderer.render(create)).toBe(
47
+ "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT DEFAULT 0 NOT NULL)",
48
+ );
49
+ expect(DefaultStatementTextRenderer.instance.render(create)).toBe(
50
+ "CREATE TABLE users (id int DEFAULT 0 NOT NULL)",
51
+ );
52
+
53
+ const keyed = new CreateTableStatement(
54
+ "users",
55
+ [{ name: "id", type: "int", identity: true }],
56
+ [new PrimaryKey("pk_users", ["id"])],
57
+ );
58
+ expect(renderer.render(keyed)).toBe("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT)");
59
+ });
60
+ });
@@ -4,7 +4,7 @@ import {
4
4
  SelectInsertedIdStatementTextRenderer,
5
5
  StatementRenderer,
6
6
  type StatementTextRenderer,
7
- } from "@deterministic-code/datasources";
7
+ } from "@datasources/core";
8
8
  import { SqliteCreateTableStatementRenderer } from "./SqliteCreateTableStatementRenderer.ts";
9
9
 
10
10
  class SqliteSelectInsertedIdStatementTextRenderer extends SelectInsertedIdStatementTextRenderer {
package/src/command.ts CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  type IReader,
20
20
  type IStatement,
21
21
  type Parameter,
22
- } from "@deterministic-code/datasources";
22
+ } from "@datasources/core";
23
23
  import type { SqliteConnection } from "./connection.ts";
24
24
  import type { SqliteStatement } from "./i-sqlite-driver.ts";
25
25
  import { reverse, SqliteRowTypeConverters } from "./converters/SqliteRowTypeConverters.ts";
package/src/connection.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  type IConnection,
8
8
  type IsolationLevelName,
9
9
  type ITransaction,
10
- } from "@deterministic-code/datasources";
10
+ } from "@datasources/core";
11
11
  import { createRequire } from "node:module";
12
12
  import { SqliteCommand } from "./command.ts";
13
13
  import type { SqliteConnectionOptions } from "./connection-options.ts";
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { BinaryConverter } from "./BinaryConverter.ts";
3
+
4
+ describe("BinaryConverter", () => {
5
+ it("maps binary", () => {
6
+ const converter = new BinaryConverter();
7
+ const bytes = new Uint8Array([1, 2]);
8
+ expect(converter.convert(bytes)).toBe(bytes);
9
+ expect(converter.reverseConvert(bytes)).toBe(bytes);
10
+ });
11
+ });
@@ -1,4 +1,4 @@
1
- import { TypeConverter, type Cell } from "@deterministic-code/datasources";
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
2
 
3
3
  export class BinaryConverter extends TypeConverter<Cell, Cell> {
4
4
  convert = (from: Cell): Cell => from;
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { BoolConverter } from "./BoolConverter.ts";
3
+
4
+ describe("BoolConverter", () => {
5
+ it("maps bool", () => {
6
+ const converter = new BoolConverter();
7
+ expect(converter.convert(1)).toBe(true);
8
+ expect(converter.convert(0)).toBe(false);
9
+ expect(converter.convert("x")).toBe("x");
10
+ expect(converter.reverseConvert(true)).toBe(1);
11
+ expect(converter.reverseConvert(false)).toBe(0);
12
+ expect(converter.reverseConvert("x")).toBe("x");
13
+ });
14
+ });
@@ -1,4 +1,4 @@
1
- import { TypeConverter, type Cell } from "@deterministic-code/datasources";
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
2
 
3
3
  export class BoolConverter extends TypeConverter<Cell, Cell> {
4
4
  convert = (from: Cell): Cell => (typeof from === "number" ? from !== 0 : from);
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { DateTimeConverter } from "./DateTimeConverter.ts";
3
+
4
+ describe("DateTimeConverter", () => {
5
+ it("maps datetime", () => {
6
+ const converter = new DateTimeConverter();
7
+ const date = new Date("2020-01-02T03:04:05.000Z");
8
+ expect(converter.convert("2020-01-02T03:04:05.000Z")).toEqual(date);
9
+ expect(converter.convert(date)).toBe(date);
10
+ expect(converter.reverseConvert(date)).toBe("2020-01-02T03:04:05.000Z");
11
+ expect(converter.reverseConvert("x")).toBe("x");
12
+ });
13
+ });
@@ -1,4 +1,4 @@
1
- import { TypeConverter, type Cell } from "@deterministic-code/datasources";
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
2
 
3
3
  export class DateTimeConverter extends TypeConverter<Cell, Cell> {
4
4
  convert = (from: Cell): Cell =>
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { DecimalConverter } from "./DecimalConverter.ts";
3
+
4
+ describe("DecimalConverter", () => {
5
+ it("maps decimal", () => {
6
+ const converter = new DecimalConverter();
7
+ expect(converter.convert("10.5")).toBe("10.5");
8
+ expect(converter.reverseConvert(10.5)).toBe("10.5");
9
+ expect(converter.reverseConvert("10.5")).toBe("10.5");
10
+ });
11
+ });
@@ -1,4 +1,4 @@
1
- import { TypeConverter, type Cell } from "@deterministic-code/datasources";
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
2
 
3
3
  export class DecimalConverter extends TypeConverter<Cell, Cell> {
4
4
  convert = (from: Cell): Cell => from;
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { SqliteDriver } from "../SqliteDriver.ts";
3
+ import { reverse, SqliteRowTypeConverters } from "./SqliteRowTypeConverters.ts";
4
+
5
+ describe("SqliteRowTypeConverters", () => {
6
+ it("registers default types", () => {
7
+ expect(Object.keys(new SqliteDriver().rowTypeConverters)).toEqual(Object.keys(SqliteRowTypeConverters));
8
+ });
9
+
10
+ it("reverses booleans, dates, and bytes", () => {
11
+ const when = new Date("2024-01-02T00:00:00.000Z");
12
+ expect(reverse(true)).toBe(1);
13
+ expect(reverse(false)).toBe(0);
14
+ expect(reverse(when)).toBe(when.toISOString());
15
+ expect(reverse(new Uint8Array([1, 2]))).toEqual(new Uint8Array([1, 2]));
16
+ expect(reverse("a")).toBe("a");
17
+ });
18
+ });
@@ -1,4 +1,4 @@
1
- import { type Cell, type TypeConverter } from "@deterministic-code/datasources";
1
+ import { type Cell, type TypeConverter } from "@datasources/core";
2
2
  import { BinaryConverter } from "./BinaryConverter.ts";
3
3
  import { BoolConverter } from "./BoolConverter.ts";
4
4
  import { DateTimeConverter } from "./DateTimeConverter.ts";
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { UuidConverter } from "./UuidConverter.ts";
3
+
4
+ describe("UuidConverter", () => {
5
+ it("maps uuid", () => {
6
+ const converter = new UuidConverter();
7
+ const id = "550e8400-e29b-41d4-a716-446655440000";
8
+ expect(converter.convert(id)).toBe(id);
9
+ expect(converter.reverseConvert(id)).toBe(id);
10
+ });
11
+ });
@@ -1,4 +1,4 @@
1
- import { TypeConverter, type Cell } from "@deterministic-code/datasources";
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
2
 
3
3
  export class UuidConverter extends TypeConverter<Cell, Cell> {
4
4
  convert = (from: Cell): Cell => from;
@@ -0,0 +1,15 @@
1
+ import { CRUDRepository, Datasource } from "@datasources/core";
2
+ import { expect, it } from "vitest";
3
+ import { SqliteDriver } from "./SqliteDriver.ts";
4
+
5
+ class Item {
6
+ id = 0;
7
+ name = "";
8
+ }
9
+
10
+ it("create from a driver", () => {
11
+ const driver = new SqliteDriver();
12
+ const ds = new Datasource("sqlite", driver, { DATASOURCE_URL: "sqlite:///:memory:" });
13
+ const repo = ds.createRepository(Item);
14
+ expect(repo instanceof CRUDRepository).toBe(true);
15
+ });
@@ -0,0 +1,95 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse } from "yaml";
4
+ import { describe, expect, it } from "vitest";
5
+ import {
6
+ expectedRepositoryConsoleSignatures,
7
+ repositoryQueries,
8
+ type RepositorySpec,
9
+ } from "../../../specs/expected-repository-console.ts";
10
+ import { repoRoot } from "../../../sql-spec-runner.ts";
11
+
12
+ describe("expected repository console", () => {
13
+ const spec = parse(
14
+ readFileSync(join(repoRoot(), "specs", "repositories", "user-crud.yaml"), "utf8"),
15
+ ) as RepositorySpec;
16
+
17
+ it("expands user-crud into statement SQL", () => {
18
+ expect(repositoryQueries(spec)).toEqual([
19
+ "INSERT INTO user (balance, created, is_enabled, location, username, uuid, version) VALUES ($balance, $created, $is_enabled, $location, $username, $uuid, $version)",
20
+ "INSERT INTO user (balance, created, is_enabled, location, username, uuid, version) VALUES ($balance, $created, $is_enabled, $location, $username, $uuid, $version)",
21
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE id = $id",
22
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE username = $username",
23
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE uuid = $uuid",
24
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE is_enabled = $is_enabled",
25
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE created = $created",
26
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user",
27
+ "UPDATE user SET balance = $balance, created = $created, is_enabled = $is_enabled, location = $location, username = $username, uuid = $uuid, version = $version WHERE uuid = $uuid",
28
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE uuid = $uuid",
29
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE created = $created",
30
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE is_enabled = $is_enabled",
31
+ "DELETE FROM user WHERE created = $created",
32
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user WHERE id = $id",
33
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM user",
34
+ ]);
35
+ });
36
+
37
+ it("expands complex-crud-mappings into flattened statement SQL", () => {
38
+ const mapped = parse(
39
+ readFileSync(join(repoRoot(), "specs", "repositories", "complex-crud-mappings.yaml"), "utf8"),
40
+ ) as RepositorySpec;
41
+ expect(repositoryQueries(mapped)).toEqual([
42
+ "INSERT INTO users (create_string, decimal_text, username, usertypename, uuid, version) VALUES ($create_string, $decimal_text, $username, $usertypename, $uuid, $version)",
43
+ "INSERT INTO users (create_string, decimal_text, username, usertypename, uuid, version) VALUES ($create_string, $decimal_text, $username, $usertypename, $uuid, $version)",
44
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE id = $id",
45
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE username = $username",
46
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE uuid = $uuid",
47
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE usertypename = $usertypename",
48
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE create_string = $create_string",
49
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users",
50
+ "UPDATE users SET create_string = $create_string, decimal_text = $decimal_text, username = $username, usertypename = $usertypename, uuid = $uuid, version = $version WHERE uuid = $uuid",
51
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE uuid = $uuid",
52
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE create_string = $create_string",
53
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE usertypename = $usertypename",
54
+ "DELETE FROM users WHERE create_string = $create_string",
55
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users WHERE id = $id",
56
+ "SELECT create_string, decimal_text, id, username, usertypename, uuid, version FROM users",
57
+ ]);
58
+ });
59
+
60
+ it("expands crud-mapper into remapped statement SQL", () => {
61
+ const mapped = parse(
62
+ readFileSync(join(repoRoot(), "specs", "repositories", "crud-mapper.yaml"), "utf8"),
63
+ ) as RepositorySpec;
64
+ const select =
65
+ "SELECT balance, created, id, is_enabled, location, username, uuid, version FROM people";
66
+ const insert =
67
+ "INSERT INTO people (balance, created, is_enabled, location, username, uuid, version) VALUES ($balance, $created, $is_enabled, $location, $username, $uuid, $version) RETURNING id";
68
+ expect(repositoryQueries(mapped)).toEqual([
69
+ insert,
70
+ insert,
71
+ `${select} WHERE id = $id LIMIT 1`,
72
+ `${select} WHERE username = $username LIMIT 1`,
73
+ `${select} WHERE username = $username LIMIT 1`,
74
+ "UPDATE people SET balance = $balance, created = $created, is_enabled = $is_enabled, location = $location, username = $username, uuid = $uuid, version = $version WHERE id = $id RETURNING id",
75
+ `${select} WHERE id = $id LIMIT 1`,
76
+ "UPDATE people SET balance = $balance, created = $created, is_enabled = $is_enabled, location = $location, username = $username, uuid = $uuid, version = $version WHERE uuid = $uuid RETURNING id",
77
+ `${select} WHERE uuid = $uuid LIMIT 1`,
78
+ "DELETE FROM people WHERE id = $id RETURNING id",
79
+ `${select} WHERE id = $id LIMIT 1`,
80
+ "DELETE FROM people WHERE username = $username RETURNING id",
81
+ `${select} WHERE id = $id LIMIT 1`,
82
+ ]);
83
+ });
84
+
85
+ it("pairs Start and Finish signatures", () => {
86
+ const signatures = expectedRepositoryConsoleSignatures(spec);
87
+ expect(signatures).toHaveLength(30);
88
+ expect(signatures[0]).toBe(
89
+ "[datasource][Start]-[INSERT INTO user (balance, created, is_enabled, location, username, uuid, version) VALUES ($balance, $created, $is_enabled, $location, $username, $uuid, $version)]",
90
+ );
91
+ expect(signatures[1]).toBe(
92
+ "[datasource][Finish]-[INSERT INTO user (balance, created, is_enabled, location, username, uuid, version) VALUES ($balance, $created, $is_enabled, $location, $username, $uuid, $version)]",
93
+ );
94
+ });
95
+ });
@@ -1,4 +1,4 @@
1
- import type { Cell, Cells, Scalar } from "@deterministic-code/datasources";
1
+ import type { Cell, Cells, Scalar } from "@datasources/core";
2
2
 
3
3
  export interface SqliteStatement {
4
4
  run(params?: Record<string, Scalar>): {
@@ -0,0 +1,296 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import {
3
+ CRUDRepository,
4
+ ReadonlyRepository,
5
+ untyped,
6
+ Datasource,
7
+ DatasourceError,
8
+ DatasourceFactory,
9
+ IsolationLevel,
10
+ type Cell,
11
+ type Cells,
12
+ columnType,
13
+ type IDriver,
14
+ RowTypeConverter,
15
+ CreateTableStatement,
16
+ Native,
17
+ ReturnStatement,
18
+ SelectStatement,
19
+ type Scalar,
20
+ } from "@datasources/core";
21
+ import { SqliteCommand } from "./command.ts";
22
+ import { SqliteConnection } from "./connection.ts";
23
+ import { SqliteRowTypeConverters } from "./converters/SqliteRowTypeConverters.ts";
24
+ import type { SqliteDatabase, SqliteStatement } from "./i-sqlite-driver.ts";
25
+ import { filenameOf, sqliteFilename } from "./sqlitePath.ts";
26
+ import { SqliteDriver } from "./SqliteDriver.ts";
27
+ import { SqliteTransaction } from "./transaction.ts";
28
+
29
+ class FakeDatabase implements SqliteDatabase {
30
+ static latest: FakeDatabase | undefined;
31
+ runs: Array<{ sql: string; params?: Record<string, Scalar> }> = [];
32
+ alls: Array<{ sql: string; params?: Record<string, Scalar> }> = [];
33
+ execs: string[] = [];
34
+ closed = false;
35
+ rows: Cells[] = [{ id: 1 }];
36
+ bySql?: Record<string, Cells[]>;
37
+ omitRowid = false;
38
+ columns?: Array<{ name: string; column: string | null; table: string | null }>;
39
+ rawRows?: Array<Array<Cell | null>>;
40
+
41
+ constructor(public filename: string) {
42
+ FakeDatabase.latest = this;
43
+ }
44
+
45
+ prepare(sql: string): SqliteStatement {
46
+ return {
47
+ run: (params) => {
48
+ this.runs.push({ sql, params });
49
+ return this.omitRowid ? { changes: 1 } : { changes: 2, lastInsertRowid: 5 };
50
+ },
51
+ all: (params) => {
52
+ this.alls.push({ sql, params });
53
+ return this.bySql?.[sql] ?? this.rows;
54
+ },
55
+ iterate: (params) => {
56
+ this.alls.push({ sql, params });
57
+ return (this.bySql?.[sql] ?? this.rows)[Symbol.iterator]();
58
+ },
59
+ columns: this.columns ? () => this.columns! : undefined,
60
+ raw: this.rawRows
61
+ ? () => ({
62
+ iterate: () => this.rawRows![Symbol.iterator](),
63
+ })
64
+ : undefined,
65
+ };
66
+ }
67
+
68
+ exec(sql: string): void {
69
+ this.execs.push(sql);
70
+ }
71
+
72
+ close(): void {
73
+ this.closed = true;
74
+ }
75
+ }
76
+
77
+ const opened = async (filename = ":memory:"): Promise<SqliteConnection> => {
78
+ const connection = new SqliteConnection({ filename, Database: FakeDatabase });
79
+ await connection.open();
80
+ return connection;
81
+ };
82
+
83
+ afterEach(() => {
84
+ DatasourceFactory.reset();
85
+ DatasourceFactory.register("sqlite", new SqliteDriver());
86
+ });
87
+
88
+ describe("sqlitePath", () => {
89
+ it("parses sqlite URLs and filenames", () => {
90
+ expect(sqliteFilename("mysql://x")).toBeUndefined();
91
+ expect(sqliteFilename("sqlite:")).toBe(":memory:");
92
+ expect(sqliteFilename("sqlite:///:memory")).toBe(":memory:");
93
+ expect(sqliteFilename("sqlite:///:memory:")).toBe(":memory:");
94
+ expect(sqliteFilename("sqlite:///tmp/db.sqlite")).toBe("tmp/db.sqlite");
95
+ expect(filenameOf({})).toBe(":memory:");
96
+ expect(filenameOf({ filename: "x.db" })).toBe("x.db");
97
+ expect(filenameOf({ url: "sqlite:///tmp/db.sqlite" })).toBe("tmp/db.sqlite");
98
+ expect(filenameOf({ url: "mysql://x" })).toBe(":memory:");
99
+ });
100
+ });
101
+
102
+ describe("SqliteConnection", () => {
103
+ it("opens, reopens, transacts, and closes", async () => {
104
+ const connection = await opened("x.db");
105
+ const db = FakeDatabase.latest!;
106
+ await expect(connection.open()).rejects.toThrow("Connection is already open");
107
+ expect(connection.isOpen).toBe(true);
108
+ expect(db.filename).toBe("x.db");
109
+ const txn = await connection.startTransaction();
110
+ await txn.commit();
111
+ const txn2 = await connection.startTransaction(IsolationLevel.ReadUncommitted);
112
+ expect(txn2.isolation).toBe(IsolationLevel.ReadUncommitted);
113
+ await txn2.abort();
114
+ expect(db.execs).toEqual([
115
+ "PRAGMA read_uncommitted = 0",
116
+ "BEGIN",
117
+ "COMMIT",
118
+ "PRAGMA read_uncommitted = 1",
119
+ "BEGIN",
120
+ "ROLLBACK",
121
+ ]);
122
+ expect(txn.isolation).toBe("readCommitted");
123
+ await connection.close();
124
+ expect(db.closed).toBe(true);
125
+ expect(() => connection.ensureOpen()).toThrow(DatasourceError);
126
+ expect(() => connection.require()).toThrow("not open");
127
+ await expect(connection.startTransaction()).rejects.toThrow(DatasourceError);
128
+ await connection.close();
129
+ });
130
+
131
+ it("defaults the filename and requires a database module to open", async () => {
132
+ const connection = new SqliteConnection();
133
+ expect(connection.filename).toBe(":memory:");
134
+ await expect(connection.open()).rejects.toThrow("SQLite driver is required");
135
+ await connection.close();
136
+ });
137
+ });
138
+
139
+ describe("SqliteCommand", () => {
140
+ it("executes SQL on the open database", async () => {
141
+ const connection = await opened();
142
+ const db = FakeDatabase.latest!;
143
+ const command = new SqliteCommand(connection);
144
+ db.rows = [{ id: 1, name: "a" }];
145
+ expect(await command.execute("DELETE FROM items", [{ name: "id", value: 1 }])).toEqual({
146
+ result: null,
147
+ rowsAffected: 2,
148
+ parameters: [{ name: "id", value: 1 }],
149
+ });
150
+ expect(await command.executeNative(new Native(""))).toEqual({ result: undefined, rowsAffected: 0 });
151
+ db.rows = [{ abs: 3 }];
152
+ const x = { name: "x", value: -3 };
153
+ expect(await command.executeStatement(new ReturnStatement("abs", [x]), [x])).toEqual({
154
+ result: 3,
155
+ rowsAffected: 1,
156
+ parameters: [x],
157
+ });
158
+ db.rows = [];
159
+ expect(await command.executeNative(new Native("SELECT 1"))).toEqual({ result: null, rowsAffected: 2 });
160
+ db.omitRowid = true;
161
+ expect(await command.executeNative(new Native("INSERT INTO items DEFAULT VALUES"))).toEqual({
162
+ result: null,
163
+ rowsAffected: 1,
164
+ });
165
+ db.rows = [{ id: 1, name: "a" }, { id: 2, name: "b" }];
166
+ const reader = await command.executeReader("SELECT * FROM items");
167
+ expect(await reader.next()).toBe(true);
168
+ expect(reader.current).toEqual({ id: 1, name: "a" });
169
+ expect(await reader.next()).toBe(true);
170
+ expect(reader.current).toEqual({ id: 2, name: "b" });
171
+ await reader.dispose();
172
+ const empty = await command.executeReaderNative(new Native(""));
173
+ expect(await empty.next()).toBe(false);
174
+ expect(await (await command.executeReaderNative(new Native(";"))).next()).toBe(false);
175
+ db.bySql = { "SELECT 1 AS id": [{ id: 1 }], "SELECT 2 AS id": [{ id: 2 }] };
176
+ const sets = await command.executeReaderNative(new Native("SELECT 1 AS id; SELECT 2 AS id"));
177
+ const first: Cells[] = [];
178
+ while (await sets.next()) first.push(sets.current!);
179
+ const moved = await sets.nextResult();
180
+ const second: Cells[] = [];
181
+ while (await sets.next()) second.push(sets.current!);
182
+ expect(moved).toBe(true);
183
+ expect(first).toEqual([{ id: 1 }]);
184
+ expect(second).toEqual([{ id: 2 }]);
185
+ expect(await sets.nextResult()).toBe(false);
186
+ await sets.dispose();
187
+ db.bySql = undefined;
188
+ db.rows = [];
189
+ expect(await command.executeStatement(new SelectStatement({ table: "dual", fields: ["1"] }))).toEqual({
190
+ result: null,
191
+ rowsAffected: 0,
192
+ parameters: undefined,
193
+ });
194
+ db.rows = [{}];
195
+ expect(await command.executeStatement(new SelectStatement({ table: "dual", fields: ["1"] }))).toMatchObject({ result: null });
196
+ db.omitRowid = false;
197
+ expect(await command.executeStatement(new CreateTableStatement("items", [{ name: "id", type: "INTEGER" }]))).toMatchObject({
198
+ result: null,
199
+ rowsAffected: 2,
200
+ });
201
+ db.columns = [
202
+ { name: "id", column: "id", table: "a" },
203
+ { name: "id", column: null, table: null },
204
+ ];
205
+ db.rawRows = [[1, null]];
206
+ const qualified = await command.executeReaderNative(new Native("SELECT a.id, id"));
207
+ expect(await qualified.next()).toBe(true);
208
+ expect(qualified.current).toEqual({ "a.id": 1, id: null });
209
+ expect(await qualified.next()).toBe(false);
210
+ await qualified.dispose();
211
+ db.columns = [{ name: "id", column: "id", table: "t" }];
212
+ db.rawRows = undefined;
213
+ db.rows = [{ id: 4 }];
214
+ const plain = await command.executeReaderNative(new Native("SELECT id"));
215
+ expect(await plain.next()).toBe(true);
216
+ expect(plain.current).toEqual({ id: 4 });
217
+ await plain.dispose();
218
+ await connection.close();
219
+ await expect(command.execute("DELETE FROM items")).rejects.toThrow("not open");
220
+ });
221
+ });
222
+
223
+ describe("Sqlite datasource", () => {
224
+ it("registers and constructs children", async () => {
225
+ expect(await new Datasource("sqlite", new SqliteDriver()).createConnection()).toBeInstanceOf(
226
+ SqliteConnection,
227
+ );
228
+ const of = (): IDriver => ({
229
+ connection: () => new SqliteConnection({ Database: FakeDatabase }),
230
+ command: (c) => c.createCommand(),
231
+ transaction: (c, isolation = "readCommitted") =>
232
+ new SqliteTransaction(c as SqliteConnection, isolation),
233
+ rowTypeConverters: SqliteRowTypeConverters,
234
+ columnType,
235
+ });
236
+ const ds = new Datasource("sqlite", of());
237
+ expect(ds.provider).toBe("sqlite");
238
+ const connection = await ds.createConnection();
239
+ await (connection as SqliteConnection).open();
240
+ expect(connection.createCommand()).toBeInstanceOf(SqliteCommand);
241
+ const command = await ds.createCommand(connection);
242
+ expect(command).toBeInstanceOf(SqliteCommand);
243
+ expect(command.connection).toBe(connection);
244
+ await command.execute("DELETE FROM items");
245
+ await command.executeReaderNative(new Native("SELECT 1"));
246
+ await expect(ds.createCommand()).rejects.toThrow("Connection is already open");
247
+ const repo = ds.createRepository(untyped(), connection);
248
+ expect(ds.createReadonlyRepository(untyped(), connection).constructor).toBe(ReadonlyRepository);
249
+ expect(repo).toBeInstanceOf(CRUDRepository);
250
+ expect(new Datasource("sqlite", of()).createRepository(untyped())).toBeInstanceOf(CRUDRepository);
251
+ expect(Datasource.create({ PROVIDER: "sqlite" }).provider).toBe("sqlite");
252
+ expect(SqliteConnection.from({ DATASOURCE_URL: "sqlite:///:memory:" }).filename).toBe(":memory:");
253
+ expect(SqliteConnection.from().filename).toBe(":memory:");
254
+ const provider = new SqliteDriver();
255
+ expect(provider.columnType("int")).toBe("INTEGER");
256
+ expect(provider.columnType("uuid")).toBe("TEXT");
257
+ expect(provider.columnType("string")).toBe("TEXT");
258
+ expect(provider.columnType("bool")).toBe("BOOLEAN");
259
+ expect(provider.columnType("decimal")).toBe("NUMERIC");
260
+ expect(provider.columnType("float")).toBe("REAL");
261
+ expect(provider.columnType("datetime")).toBe("TEXT");
262
+ expect(provider.columnType("binary")).toBe("BLOB");
263
+ expect(() => provider.columnType("xml")).toThrow(DatasourceError);
264
+ expect(provider.command(connection)).toBeInstanceOf(SqliteCommand);
265
+ expect(provider.transaction(connection as SqliteConnection).isolation).toBe("readCommitted");
266
+ });
267
+
268
+ it("maps property targets", () => {
269
+ const provider = new SqliteDriver();
270
+ const created = "2020-01-02T03:04:05.000Z";
271
+ const converter = RowTypeConverter.from(provider.rowTypeConverters, {
272
+ Key: "uuid",
273
+ Active: "bool",
274
+ Login: "string",
275
+ Created: "datetime",
276
+ });
277
+ const row = converter.convert({
278
+ Key: "11111111-1111-1111-1111-111111111111",
279
+ Active: 1,
280
+ Login: "ada",
281
+ Created: created,
282
+ });
283
+ expect(row).toEqual({
284
+ Key: "11111111-1111-1111-1111-111111111111",
285
+ Active: true,
286
+ Login: "ada",
287
+ Created: new Date(created),
288
+ });
289
+ expect(converter.reverseConvert(row)).toEqual({
290
+ Key: "11111111-1111-1111-1111-111111111111",
291
+ Active: 1,
292
+ Login: "ada",
293
+ Created: created,
294
+ });
295
+ });
296
+ });
@@ -2,7 +2,7 @@ import {
2
2
  IsolationLevel,
3
3
  type ITransaction,
4
4
  type IsolationLevelName,
5
- } from "@deterministic-code/datasources";
5
+ } from "@datasources/core";
6
6
  import type { SqliteConnection } from "./connection.ts";
7
7
 
8
8
  export class SqliteTransaction implements ITransaction {