mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #113 from bknd-io/feat/data-sqlocal-connection
add sqlocal connection including example
This commit is contained in:
@@ -75,6 +75,7 @@ export type DbFunctions = {
|
|||||||
const CONN_SYMBOL = Symbol.for("bknd:connection");
|
const CONN_SYMBOL = Symbol.for("bknd:connection");
|
||||||
|
|
||||||
export abstract class Connection<DB = any> {
|
export abstract class Connection<DB = any> {
|
||||||
|
protected initialized = false;
|
||||||
kysely: Kysely<DB>;
|
kysely: Kysely<DB>;
|
||||||
protected readonly supported = {
|
protected readonly supported = {
|
||||||
batching: false,
|
batching: false,
|
||||||
@@ -89,6 +90,11 @@ export abstract class Connection<DB = any> {
|
|||||||
this[CONN_SYMBOL] = true;
|
this[CONN_SYMBOL] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @todo: consider moving constructor logic here, required by sqlocal
|
||||||
|
async init(): Promise<void> {
|
||||||
|
this.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a helper function to manage Connection classes
|
* This is a helper function to manage Connection classes
|
||||||
* coming from different places
|
* coming from different places
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ export class ModuleManager {
|
|||||||
|
|
||||||
async build(opts?: { fetch?: boolean }) {
|
async build(opts?: { fetch?: boolean }) {
|
||||||
this.logger.context("build").log("version", this.version());
|
this.logger.context("build").log("version", this.version());
|
||||||
|
await this.ctx().connection.init();
|
||||||
|
|
||||||
// if no config provided, try fetch from db
|
// if no config provided, try fetch from db
|
||||||
if (this.version() === 0 || opts?.fetch === true) {
|
if (this.version() === 0 || opts?.fetch === true) {
|
||||||
|
|||||||
@@ -1,38 +1,48 @@
|
|||||||
import { Api, type ApiOptions, type TApiUser } from "Api";
|
import { Api, type ApiOptions, type TApiUser } from "Api";
|
||||||
import { isDebug } from "core";
|
import { isDebug } from "core";
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, type ReactNode, useContext } from "react";
|
||||||
|
|
||||||
const ClientContext = createContext<{ baseUrl: string; api: Api }>({
|
const ClientContext = createContext<{ baseUrl: string; api: Api }>({
|
||||||
baseUrl: undefined,
|
baseUrl: undefined,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
export type ClientProviderProps = {
|
export type ClientProviderProps = {
|
||||||
children?: any;
|
children?: ReactNode;
|
||||||
baseUrl?: string;
|
} & (
|
||||||
user?: TApiUser | null | undefined;
|
| { baseUrl?: string; user?: TApiUser | null | undefined }
|
||||||
};
|
| {
|
||||||
|
api: Api;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const ClientProvider = ({ children, baseUrl, user }: ClientProviderProps) => {
|
export const ClientProvider = ({ children, ...props }: ClientProviderProps) => {
|
||||||
const winCtx = useBkndWindowContext();
|
let api: Api;
|
||||||
const _ctx_baseUrl = useBaseUrl();
|
|
||||||
let actualBaseUrl = baseUrl ?? _ctx_baseUrl ?? "";
|
|
||||||
|
|
||||||
try {
|
if (props && "api" in props) {
|
||||||
if (!baseUrl) {
|
api = props.api;
|
||||||
if (_ctx_baseUrl) {
|
} else {
|
||||||
actualBaseUrl = _ctx_baseUrl;
|
const winCtx = useBkndWindowContext();
|
||||||
console.warn("wrapped many times, take from context", actualBaseUrl);
|
const _ctx_baseUrl = useBaseUrl();
|
||||||
} else if (typeof window !== "undefined") {
|
const { baseUrl, user } = props;
|
||||||
actualBaseUrl = window.location.origin;
|
let actualBaseUrl = baseUrl ?? _ctx_baseUrl ?? "";
|
||||||
//console.log("setting from window", actualBaseUrl);
|
|
||||||
|
try {
|
||||||
|
if (!baseUrl) {
|
||||||
|
if (_ctx_baseUrl) {
|
||||||
|
actualBaseUrl = _ctx_baseUrl;
|
||||||
|
console.warn("wrapped many times, take from context", actualBaseUrl);
|
||||||
|
} else if (typeof window !== "undefined") {
|
||||||
|
actualBaseUrl = window.location.origin;
|
||||||
|
//console.log("setting from window", actualBaseUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error in ClientProvider", e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error("Error in ClientProvider", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log("api init", { host: actualBaseUrl, user: user ?? winCtx.user });
|
//console.log("api init", { host: actualBaseUrl, user: user ?? winCtx.user });
|
||||||
const api = new Api({ host: actualBaseUrl, user: user ?? winCtx.user, verbose: isDebug() });
|
api = new Api({ host: actualBaseUrl, user: user ?? winCtx.user, verbose: isDebug() });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClientContext.Provider value={{ baseUrl: api.baseUrl, api }}>
|
<ClientContext.Provider value={{ baseUrl: api.baseUrl, api }}>
|
||||||
|
|||||||
24
examples/react/.gitignore
vendored
Normal file
24
examples/react/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
1
examples/react/README.md
Normal file
1
examples/react/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# local bknd POC (WIP)
|
||||||
13
examples/react/index.html
Normal file
13
examples/react/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
28
examples/react/package.json
Normal file
28
examples/react/package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "react",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bknd": "file:../../app",
|
||||||
|
"@bknd/sqlocal": "file:../../packages/sqlocal",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"sqlocal": "^0.14.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.10",
|
||||||
|
"@types/react-dom": "^19.0.4",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"globals": "^15.15.0",
|
||||||
|
"typescript": "~5.7.2",
|
||||||
|
"typescript-eslint": "^8.24.1",
|
||||||
|
"vite": "^6.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
71
examples/react/src/App.tsx
Normal file
71
examples/react/src/App.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { App } from "bknd";
|
||||||
|
import { Admin } from "bknd/ui";
|
||||||
|
import { checksum } from "bknd/utils";
|
||||||
|
import { em, entity, text } from "bknd/data";
|
||||||
|
import { SQLocalConnection } from "@bknd/sqlocal";
|
||||||
|
import "bknd/dist/styles.css";
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const [app, setApp] = useState<App | undefined>(undefined);
|
||||||
|
const [hash, setHash] = useState<string>("");
|
||||||
|
|
||||||
|
async function onBuilt(app: App) {
|
||||||
|
setApp(app);
|
||||||
|
setHash(await checksum(app.toJSON()));
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setup({
|
||||||
|
onBuilt,
|
||||||
|
})
|
||||||
|
.then((app) => console.log("setup", app?.version()))
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!app) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
<Admin key={hash} withProvider={{ api: app.getApi() }} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
export async function setup(opts?: {
|
||||||
|
beforeBuild?: (app: App) => Promise<void>;
|
||||||
|
onBuilt?: (app: App) => Promise<void>;
|
||||||
|
}) {
|
||||||
|
if (initialized) return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
const connection = new SQLocalConnection({
|
||||||
|
verbose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = App.create({
|
||||||
|
connection,
|
||||||
|
initialConfig: {
|
||||||
|
data: em({
|
||||||
|
test: entity("test", {
|
||||||
|
name: text(),
|
||||||
|
}),
|
||||||
|
}).toJSON(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opts?.onBuilt) {
|
||||||
|
app.emgr.onEvent(
|
||||||
|
App.Events.AppBuiltEvent,
|
||||||
|
async () => {
|
||||||
|
await opts.onBuilt?.(app);
|
||||||
|
},
|
||||||
|
"sync",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await opts?.beforeBuild?.(app);
|
||||||
|
await app.build({ sync: true });
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
9
examples/react/src/main.tsx
Normal file
9
examples/react/src/main.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import App from "./App.tsx";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
1
examples/react/src/vite-env.d.ts
vendored
Normal file
1
examples/react/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
22
examples/react/tsconfig.json
Normal file
22
examples/react/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
23
examples/react/vite.config.ts
Normal file
23
examples/react/vite.config.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
// https://sqlocal.dallashoffman.com/guide/setup#vite-configuration
|
||||||
|
export default defineConfig({
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ["sqlocal"],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
{
|
||||||
|
name: "configure-response-headers",
|
||||||
|
configureServer: (server) => {
|
||||||
|
server.middlewares.use((_req, res, next) => {
|
||||||
|
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||||
|
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
29
packages/sqlocal/README.md
Normal file
29
packages/sqlocal/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# SQLocal adapter for `bknd` (experimental)
|
||||||
|
This packages adds an adapter to use a SQLocal database with `bknd`. It is based on [`sqlocal`](https://github.com/DallasHoff/sqlocal) and the driver included for `kysely`.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Install the adapter with:
|
||||||
|
```bash
|
||||||
|
npm install @bknd/sqlocal
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Create a connection:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { SQLocalConnection } from "@bknd/sqlocal";
|
||||||
|
|
||||||
|
const connection = new SQLocalConnection({
|
||||||
|
databasePath: "db.sqlite"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the connection depending on which framework or runtime you are using. E.g., when using `createApp`, you can use the connection as follows:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createApp } from "bknd";
|
||||||
|
import { SQLocalConnection } from "@bknd/sqlocal";
|
||||||
|
|
||||||
|
const connection = new SQLocalConnection();
|
||||||
|
const app = createApp({ connection });
|
||||||
|
```
|
||||||
39
packages/sqlocal/package.json
Normal file
39
packages/sqlocal/package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "@bknd/sqlocal",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"test": "vitest",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"prepublishOnly": "bun run test && bun run typecheck && bun run build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"sqlocal": "^0.14.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitest/browser": "^3.0.8",
|
||||||
|
"@vitest/ui": "^3.0.8",
|
||||||
|
"@types/node": "^22.13.10",
|
||||||
|
"bknd": "workspace:*",
|
||||||
|
"kysely": "^0.27.6",
|
||||||
|
"tsup": "^8.4.0",
|
||||||
|
"typescript": "^5.6.3",
|
||||||
|
"vitest": "^3.0.8",
|
||||||
|
"webdriverio": "^9.12.0"
|
||||||
|
},
|
||||||
|
"tsup": {
|
||||||
|
"entry": ["src/index.ts"],
|
||||||
|
"format": ["esm"],
|
||||||
|
"target": "es2022",
|
||||||
|
"clean": true,
|
||||||
|
"minify": true,
|
||||||
|
"dts": true,
|
||||||
|
"metafile": true,
|
||||||
|
"external": ["bknd", "kysely"]
|
||||||
|
},
|
||||||
|
"files": ["dist", "README.md", "!*.map", "!metafile*.json"]
|
||||||
|
}
|
||||||
51
packages/sqlocal/src/SQLocalConnection.ts
Normal file
51
packages/sqlocal/src/SQLocalConnection.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Kysely, ParseJSONResultsPlugin } from "kysely";
|
||||||
|
import { SqliteConnection, SqliteIntrospector } from "bknd/data";
|
||||||
|
import { SQLocalKysely } from "sqlocal/kysely";
|
||||||
|
import type { ClientConfig } from "sqlocal";
|
||||||
|
|
||||||
|
const plugins = [new ParseJSONResultsPlugin()];
|
||||||
|
|
||||||
|
export type SQLocalConnectionConfig = Omit<ClientConfig, "databasePath"> & {
|
||||||
|
// make it optional
|
||||||
|
databasePath?: ClientConfig["databasePath"];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SQLocalConnection extends SqliteConnection {
|
||||||
|
private _client: SQLocalKysely | undefined;
|
||||||
|
|
||||||
|
constructor(private config: SQLocalConnectionConfig) {
|
||||||
|
super(null as any, {}, plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async init() {
|
||||||
|
if (this.initialized) return;
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
this._client = new SQLocalKysely({
|
||||||
|
...this.config,
|
||||||
|
databasePath: this.config.databasePath ?? "session",
|
||||||
|
onConnect: (r) => {
|
||||||
|
this.kysely = new Kysely<any>({
|
||||||
|
dialect: {
|
||||||
|
...this._client!.dialect,
|
||||||
|
createIntrospector: (db: Kysely<any>) => {
|
||||||
|
return new SqliteIntrospector(db, {
|
||||||
|
plugins,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins,
|
||||||
|
});
|
||||||
|
this.config.onConnect?.(r);
|
||||||
|
resolve(1);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
get client(): SQLocalKysely {
|
||||||
|
if (!this._client) throw new Error("Client not initialized");
|
||||||
|
return this._client!;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/sqlocal/src/index.ts
Normal file
1
packages/sqlocal/src/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { SQLocalConnection, type SQLocalConnectionConfig } from "./SQLocalConnection";
|
||||||
10
packages/sqlocal/test/base.test.ts
Normal file
10
packages/sqlocal/test/base.test.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { SQLocal } from "sqlocal";
|
||||||
|
|
||||||
|
describe("base", () => {
|
||||||
|
const { sql } = new SQLocal(":memory:");
|
||||||
|
|
||||||
|
it("works", async () => {
|
||||||
|
expect(await sql`SELECT 1`).toEqual([{ "1": 1 }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
16
packages/sqlocal/test/connection.test.ts
Normal file
16
packages/sqlocal/test/connection.test.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { SQLocalConnection, type SQLocalConnectionConfig } from "../src";
|
||||||
|
|
||||||
|
describe(SQLocalConnection, () => {
|
||||||
|
function create(config: SQLocalConnectionConfig = {}) {
|
||||||
|
return new SQLocalConnection(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("constructs", async () => {
|
||||||
|
const connection = create();
|
||||||
|
expect(() => connection.client).toThrow();
|
||||||
|
await connection.init();
|
||||||
|
expect(connection.client).toBeDefined();
|
||||||
|
expect(await connection.client.sql`SELECT 1`).toEqual([{ "1": 1 }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
100
packages/sqlocal/test/integration.test.ts
Normal file
100
packages/sqlocal/test/integration.test.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { SQLocalConnection, type SQLocalConnectionConfig } from "../src";
|
||||||
|
import { createApp } from "bknd";
|
||||||
|
import * as proto from "bknd/data";
|
||||||
|
|
||||||
|
describe("integration", () => {
|
||||||
|
function create(config: SQLocalConnectionConfig = { databasePath: ":memory:" }) {
|
||||||
|
return new SQLocalConnection(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should create app and ping", async () => {
|
||||||
|
const app = createApp({
|
||||||
|
connection: create(),
|
||||||
|
});
|
||||||
|
await app.build();
|
||||||
|
|
||||||
|
expect(app.version()).toBeDefined();
|
||||||
|
expect(await app.em.ping()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a basic schema", async () => {
|
||||||
|
const schema = proto.em(
|
||||||
|
{
|
||||||
|
posts: proto.entity("posts", {
|
||||||
|
title: proto.text().required(),
|
||||||
|
content: proto.text(),
|
||||||
|
}),
|
||||||
|
comments: proto.entity("comments", {
|
||||||
|
content: proto.text(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
(fns, s) => {
|
||||||
|
fns.relation(s.comments).manyToOne(s.posts);
|
||||||
|
fns.index(s.posts).on(["title"], true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
connection: create(),
|
||||||
|
initialConfig: {
|
||||||
|
data: schema.toJSON(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.build();
|
||||||
|
|
||||||
|
expect(app.em.entities.length).toBe(2);
|
||||||
|
expect(app.em.entities.map((e) => e.name)).toEqual(["posts", "comments"]);
|
||||||
|
|
||||||
|
const api = app.getApi();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await api.data.createMany("posts", [
|
||||||
|
{
|
||||||
|
title: "Hello",
|
||||||
|
content: "World",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Hello 2",
|
||||||
|
content: "World 2",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
).data,
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Hello",
|
||||||
|
content: "World",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Hello 2",
|
||||||
|
content: "World 2",
|
||||||
|
},
|
||||||
|
] as any);
|
||||||
|
|
||||||
|
// try to create an existing
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await api.data.createOne("posts", {
|
||||||
|
title: "Hello",
|
||||||
|
})
|
||||||
|
).ok,
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
// add a comment to a post
|
||||||
|
await api.data.createOne("comments", {
|
||||||
|
content: "Hello",
|
||||||
|
posts_id: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// and then query using a `with` property
|
||||||
|
const result = await api.data.readMany("posts", { with: ["comments"] });
|
||||||
|
expect(result.length).toBe(2);
|
||||||
|
expect(result[0].comments.length).toBe(1);
|
||||||
|
expect(result[0].comments[0].content).toBe("Hello");
|
||||||
|
expect(result[1].comments.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
29
packages/sqlocal/tsconfig.json
Normal file
29
packages/sqlocal/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": false,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": false,
|
||||||
|
"target": "ES2022",
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"allowJs": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"declaration": true,
|
||||||
|
"strict": true,
|
||||||
|
"allowUnusedLabels": false,
|
||||||
|
"allowUnreachableCode": false,
|
||||||
|
"exactOptionalPropertyTypes": false,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
36
packages/sqlocal/vitest.config.ts
Normal file
36
packages/sqlocal/vitest.config.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/// <reference types="vitest" />
|
||||||
|
/// <reference types="@vitest/browser/providers/webdriverio" />
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
// https://github.com/DallasHoff/sqlocal/blob/main/vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
testTimeout: 1000,
|
||||||
|
hookTimeout: 1000,
|
||||||
|
teardownTimeout: 1000,
|
||||||
|
includeTaskLocation: true,
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
headless: true,
|
||||||
|
screenshotFailures: false,
|
||||||
|
provider: "webdriverio",
|
||||||
|
instances: [{ browser: "chrome" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ["@sqlite.org/sqlite-wasm"],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
enforce: "pre",
|
||||||
|
name: "configure-response-headers",
|
||||||
|
configureServer: (server) => {
|
||||||
|
server.middlewares.use((_req, res, next) => {
|
||||||
|
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||||
|
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user