@gblikas/querykit 0.0.0
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.
- package/.cursor/BUGBOT.md +21 -0
- package/.cursor/rules/01-project-structure.mdc +77 -0
- package/.cursor/rules/02-typescript-standards.mdc +105 -0
- package/.cursor/rules/03-testing-standards.mdc +78 -0
- package/.cursor/rules/04-query-language.mdc +79 -0
- package/.cursor/rules/05-solid-principles.mdc +118 -0
- package/.cursor/rules/liqe-readme-docs.mdc +438 -0
- package/.devcontainer/devcontainer.json +25 -0
- package/.eslintignore +1 -0
- package/.eslintrc.js +39 -0
- package/.github/dependabot.yml +12 -0
- package/.github/workflows/ci.yml +114 -0
- package/.github/workflows/publish.yml +61 -0
- package/.husky/pre-commit +30 -0
- package/.prettierrc +10 -0
- package/CONTRIBUTING.md +187 -0
- package/LICENSE +674 -0
- package/README.md +237 -0
- package/dist/adapters/drizzle/index.d.ts +122 -0
- package/dist/adapters/drizzle/index.js +166 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +25 -0
- package/dist/adapters/types.d.ts +60 -0
- package/dist/adapters/types.js +8 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +118 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +18 -0
- package/dist/parser/parser.d.ts +51 -0
- package/dist/parser/parser.js +201 -0
- package/dist/parser/types.d.ts +68 -0
- package/dist/parser/types.js +5 -0
- package/dist/query/builder.d.ts +61 -0
- package/dist/query/builder.js +188 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +18 -0
- package/dist/query/types.d.ts +79 -0
- package/dist/query/types.js +2 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.js +18 -0
- package/dist/security/types.d.ts +181 -0
- package/dist/security/types.js +43 -0
- package/dist/security/validator.d.ts +191 -0
- package/dist/security/validator.js +344 -0
- package/dist/translators/drizzle/index.d.ts +73 -0
- package/dist/translators/drizzle/index.js +260 -0
- package/dist/translators/index.d.ts +8 -0
- package/dist/translators/index.js +27 -0
- package/dist/translators/sql/index.d.ts +108 -0
- package/dist/translators/sql/index.js +252 -0
- package/dist/translators/types.d.ts +39 -0
- package/dist/translators/types.js +8 -0
- package/examples/qk-next/README.md +35 -0
- package/examples/qk-next/app/favicon.ico +0 -0
- package/examples/qk-next/app/globals.css +122 -0
- package/examples/qk-next/app/layout.tsx +121 -0
- package/examples/qk-next/app/page.tsx +813 -0
- package/examples/qk-next/app/providers.tsx +80 -0
- package/examples/qk-next/components/aurora-background.tsx +12 -0
- package/examples/qk-next/components/github-stars.tsx +51 -0
- package/examples/qk-next/components/mode-toggle.tsx +27 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/Aurora/Aurora.tsx +217 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/LightRays/LightRays.tsx +474 -0
- package/examples/qk-next/components/theme-provider.tsx +11 -0
- package/examples/qk-next/components/ui/card.tsx +92 -0
- package/examples/qk-next/components/ui/command.tsx +184 -0
- package/examples/qk-next/components/ui/dialog.tsx +143 -0
- package/examples/qk-next/components/ui/drawer.tsx +135 -0
- package/examples/qk-next/components/ui/hover-card.tsx +44 -0
- package/examples/qk-next/components/ui/icons.tsx +148 -0
- package/examples/qk-next/components/ui/sonner.tsx +26 -0
- package/examples/qk-next/components/ui/table.tsx +117 -0
- package/examples/qk-next/components.json +21 -0
- package/examples/qk-next/eslint.config.mjs +21 -0
- package/examples/qk-next/jsrepo.json +13 -0
- package/examples/qk-next/lib/utils.ts +6 -0
- package/examples/qk-next/next.config.ts +8 -0
- package/examples/qk-next/package.json +48 -0
- package/examples/qk-next/pnpm-lock.yaml +5558 -0
- package/examples/qk-next/postcss.config.mjs +5 -0
- package/examples/qk-next/public/file.svg +1 -0
- package/examples/qk-next/public/globe.svg +1 -0
- package/examples/qk-next/public/next.svg +1 -0
- package/examples/qk-next/public/vercel.svg +1 -0
- package/examples/qk-next/public/window.svg +1 -0
- package/examples/qk-next/tsconfig.json +42 -0
- package/examples/qk-next/types/sonner.d.ts +3 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +115 -0
- package/src/adapters/drizzle/index.ts +299 -0
- package/src/adapters/index.ts +11 -0
- package/src/adapters/types.ts +72 -0
- package/src/index.ts +194 -0
- package/src/integration.test.ts +202 -0
- package/src/parser/index.ts +2 -0
- package/src/parser/parser.test.ts +1056 -0
- package/src/parser/parser.ts +268 -0
- package/src/parser/types.ts +97 -0
- package/src/query/builder.test.ts +272 -0
- package/src/query/builder.ts +274 -0
- package/src/query/index.ts +2 -0
- package/src/query/types.ts +107 -0
- package/src/security/index.ts +2 -0
- package/src/security/types.ts +210 -0
- package/src/security/validator.test.ts +459 -0
- package/src/security/validator.ts +395 -0
- package/src/security.test.ts +366 -0
- package/src/translators/drizzle/drizzle-translator.test.ts +128 -0
- package/src/translators/drizzle/index.test.ts +45 -0
- package/src/translators/drizzle/index.ts +346 -0
- package/src/translators/index.ts +14 -0
- package/src/translators/sql/index.test.ts +45 -0
- package/src/translators/sql/index.ts +331 -0
- package/src/translators/sql/sql-translator.test.ts +419 -0
- package/src/translators/types.ts +44 -0
- package/src/types/sonner.d.ts +3 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"module": "esnext",
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"jsx": "preserve",
|
|
19
|
+
"incremental": true,
|
|
20
|
+
"plugins": [
|
|
21
|
+
{
|
|
22
|
+
"name": "next"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./*"
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
"baseUrl": "."
|
|
31
|
+
},
|
|
32
|
+
"include": [
|
|
33
|
+
"next-env.d.ts",
|
|
34
|
+
"**/*.ts",
|
|
35
|
+
"**/*.tsx",
|
|
36
|
+
"types/**/*.d.ts",
|
|
37
|
+
".next/types/**/*.ts"
|
|
38
|
+
],
|
|
39
|
+
"exclude": [
|
|
40
|
+
"node_modules"
|
|
41
|
+
]
|
|
42
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
roots: ['<rootDir>/src'],
|
|
6
|
+
testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
|
|
7
|
+
transform: {
|
|
8
|
+
'^.+\\.tsx?$': [
|
|
9
|
+
'ts-jest',
|
|
10
|
+
{
|
|
11
|
+
diagnostics: {
|
|
12
|
+
ignoreCodes: [6133, 6196, 2322]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
verbose: true,
|
|
18
|
+
collectCoverage: true,
|
|
19
|
+
collectCoverageFrom: [
|
|
20
|
+
'src/**/*.ts',
|
|
21
|
+
'!src/**/*.test.ts',
|
|
22
|
+
'!src/**/*.spec.ts',
|
|
23
|
+
'!src/**/index.ts',
|
|
24
|
+
'!src/types/**/*.ts'
|
|
25
|
+
]
|
|
26
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gblikas/querykit",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A comprehensive query toolkit for TypeScript that simplifies building and executing data queries across different environments",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"lint-staged": {
|
|
8
|
+
"*.{ts,tsx}": [
|
|
9
|
+
"eslint --fix",
|
|
10
|
+
"prettier --write"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"query",
|
|
15
|
+
"filter",
|
|
16
|
+
"typescript",
|
|
17
|
+
"orm",
|
|
18
|
+
"database",
|
|
19
|
+
"drizzle"
|
|
20
|
+
],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"drizzle-orm": "^0.30.2",
|
|
28
|
+
"liqe": "^3.3.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/jest": "^29.5.12",
|
|
32
|
+
"@types/node": "^20.11.24",
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
|
34
|
+
"@typescript-eslint/parser": "^7.1.0",
|
|
35
|
+
"eslint": "^8.57.0",
|
|
36
|
+
"eslint-config-prettier": "^9.1.0",
|
|
37
|
+
"husky": "^9.0.11",
|
|
38
|
+
"jest": "^29.7.0",
|
|
39
|
+
"lint-staged": "^15.5.1",
|
|
40
|
+
"prettier": "^3.2.5",
|
|
41
|
+
"ts-jest": "^29.1.2",
|
|
42
|
+
"typescript": "^5.3.3"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc --outDir dist",
|
|
46
|
+
"test": "jest",
|
|
47
|
+
"lint": "eslint src --ext .ts",
|
|
48
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
49
|
+
"lint-staged": "lint-staged"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { DrizzleAdapter, IDrizzleDatabase } from './index';
|
|
2
|
+
import { QueryParser } from '../../parser';
|
|
3
|
+
import { QueryExpression } from '../../parser/types';
|
|
4
|
+
import { SQLWrapper, sql } from 'drizzle-orm';
|
|
5
|
+
|
|
6
|
+
// Create mocks
|
|
7
|
+
const mockWhere = jest.fn().mockReturnThis();
|
|
8
|
+
const mockOrderBy = jest.fn().mockReturnThis();
|
|
9
|
+
const mockLimit = jest.fn().mockReturnThis();
|
|
10
|
+
const mockOffset = jest.fn().mockReturnThis();
|
|
11
|
+
const mockFrom = jest.fn().mockReturnValue({
|
|
12
|
+
where: mockWhere,
|
|
13
|
+
orderBy: mockOrderBy,
|
|
14
|
+
limit: mockLimit,
|
|
15
|
+
offset: mockOffset,
|
|
16
|
+
then: <T>(callback: (value: unknown[]) => T) =>
|
|
17
|
+
Promise.resolve(callback([{ id: 1, title: 'Test Todo' }]))
|
|
18
|
+
});
|
|
19
|
+
const mockSelect = jest.fn().mockReturnValue({ from: mockFrom });
|
|
20
|
+
|
|
21
|
+
// Create a mock DB instance
|
|
22
|
+
const mockDb: IDrizzleDatabase = {
|
|
23
|
+
select: mockSelect
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Create mock schema with SQLWrapper fields
|
|
27
|
+
const mockSchema = {
|
|
28
|
+
todos: {
|
|
29
|
+
id: sql.raw('id') as unknown as SQLWrapper,
|
|
30
|
+
title: sql.raw('title') as unknown as SQLWrapper,
|
|
31
|
+
priority: sql.raw('priority') as unknown as SQLWrapper,
|
|
32
|
+
status: sql.raw('status') as unknown as SQLWrapper
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('DrizzleAdapter', () => {
|
|
37
|
+
let adapter: DrizzleAdapter;
|
|
38
|
+
let parser: QueryParser;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
// Reset mocks
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
|
|
44
|
+
// Create fresh instances
|
|
45
|
+
adapter = new DrizzleAdapter();
|
|
46
|
+
parser = new QueryParser();
|
|
47
|
+
|
|
48
|
+
// Initialize the adapter
|
|
49
|
+
adapter.initialize({
|
|
50
|
+
db: mockDb,
|
|
51
|
+
schema: mockSchema
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('execute', () => {
|
|
56
|
+
it('should execute a simple query', async () => {
|
|
57
|
+
const expression = parser.parse('title:"Test Todo"');
|
|
58
|
+
const result = await adapter.execute('todos', expression);
|
|
59
|
+
|
|
60
|
+
expect(mockSelect).toHaveBeenCalled();
|
|
61
|
+
expect(mockFrom).toHaveBeenCalled();
|
|
62
|
+
expect(mockWhere).toHaveBeenCalled();
|
|
63
|
+
expect(result).toEqual([{ id: 1, title: 'Test Todo' }]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should support ordering', async () => {
|
|
67
|
+
const expression = parser.parse('status:"active"');
|
|
68
|
+
await adapter.execute('todos', expression, {
|
|
69
|
+
orderBy: { title: 'asc' }
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(mockOrderBy).toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should support limit', async () => {
|
|
76
|
+
const expression = parser.parse('status:"active"');
|
|
77
|
+
await adapter.execute('todos', expression, {
|
|
78
|
+
limit: 10
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(mockLimit).toHaveBeenCalledWith(10);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should support offset', async () => {
|
|
85
|
+
const expression = parser.parse('status:"active"');
|
|
86
|
+
await adapter.execute('todos', expression, {
|
|
87
|
+
offset: 20
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(mockOffset).toHaveBeenCalledWith(20);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should throw error if table not found', async () => {
|
|
94
|
+
const expression = parser.parse('status:"active"');
|
|
95
|
+
|
|
96
|
+
await expect(
|
|
97
|
+
adapter.execute('unknown_table', expression)
|
|
98
|
+
).rejects.toThrow('Table unknown_table not found in schema');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('canExecute', () => {
|
|
103
|
+
it('should return true for valid expressions', () => {
|
|
104
|
+
const expression = parser.parse('status:"active"');
|
|
105
|
+
expect(adapter.canExecute(expression)).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return false for invalid expressions', () => {
|
|
109
|
+
const invalidExpression = {
|
|
110
|
+
type: 'unsupported'
|
|
111
|
+
} as unknown as QueryExpression;
|
|
112
|
+
expect(adapter.canExecute(invalidExpression)).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle ORM Adapter for QueryKit
|
|
3
|
+
*
|
|
4
|
+
* This adapter connects QueryKit to Drizzle ORM for database queries.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { DrizzleTranslator } from '../../translators/drizzle';
|
|
8
|
+
import { IAdapter, IAdapterOptions, IQueryExecutionOptions } from '../types';
|
|
9
|
+
import { QueryExpression } from '../../parser/types';
|
|
10
|
+
import { SQL, SQLWrapper, asc, desc, sql } from 'drizzle-orm';
|
|
11
|
+
import { createQueryKit, QueryKit } from '../../index';
|
|
12
|
+
/**
|
|
13
|
+
* Type for Drizzle ORM database instance
|
|
14
|
+
*/
|
|
15
|
+
export interface IDrizzleDatabase {
|
|
16
|
+
select: () => { from: (table: unknown) => IDrizzleQueryBuilder };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Type for Drizzle query builder
|
|
21
|
+
*/
|
|
22
|
+
export interface IDrizzleQueryBuilder {
|
|
23
|
+
where: (condition: SQL) => IDrizzleQueryBuilder;
|
|
24
|
+
orderBy: (...clauses: SQL[]) => IDrizzleQueryBuilder;
|
|
25
|
+
limit: (limit: number) => IDrizzleQueryBuilder;
|
|
26
|
+
offset: (offset: number) => IDrizzleQueryBuilder;
|
|
27
|
+
// This is already a Promise due to Drizzle's thenable implementation
|
|
28
|
+
[Symbol.toStringTag]: string;
|
|
29
|
+
then<TResult1 = unknown, TResult2 = never>(
|
|
30
|
+
onfulfilled?:
|
|
31
|
+
| ((value: unknown[]) => TResult1 | PromiseLike<TResult1>)
|
|
32
|
+
| null,
|
|
33
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
|
|
34
|
+
): Promise<TResult1 | TResult2>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options specific to the Drizzle adapter
|
|
39
|
+
*/
|
|
40
|
+
export interface IDrizzleAdapterOptions<
|
|
41
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>
|
|
42
|
+
> extends IAdapterOptions {
|
|
43
|
+
/**
|
|
44
|
+
* The Drizzle ORM database instance
|
|
45
|
+
*/
|
|
46
|
+
db: IDrizzleDatabase | unknown;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Schema information with Drizzle table definitions
|
|
50
|
+
*/
|
|
51
|
+
schema: TSchema;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether to normalize field names (e.g., lowercase them)
|
|
55
|
+
*/
|
|
56
|
+
normalizeFieldNames?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Options for Drizzle query execution
|
|
61
|
+
*/
|
|
62
|
+
export interface IDrizzleQueryExecutionOptions extends IQueryExecutionOptions {
|
|
63
|
+
/**
|
|
64
|
+
* Sort fields in the format: { field: 'asc' | 'desc' }
|
|
65
|
+
*/
|
|
66
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Maximum number of records to return
|
|
70
|
+
*/
|
|
71
|
+
limit?: number;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Number of records to skip
|
|
75
|
+
*/
|
|
76
|
+
offset?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Error thrown when adapter operations fail
|
|
81
|
+
*/
|
|
82
|
+
export class DrizzleAdapterError extends Error {
|
|
83
|
+
constructor(message: string) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = 'DrizzleAdapterError';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Adapter for Drizzle ORM
|
|
91
|
+
*/
|
|
92
|
+
export class DrizzleAdapter<
|
|
93
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>
|
|
94
|
+
> implements IAdapter<IDrizzleAdapterOptions<TSchema>>
|
|
95
|
+
{
|
|
96
|
+
private db!: unknown;
|
|
97
|
+
private schema!: TSchema;
|
|
98
|
+
private translator!: DrizzleTranslator;
|
|
99
|
+
private initialized: boolean = false;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Optionally initialize via constructor for convenience
|
|
103
|
+
*/
|
|
104
|
+
constructor(options?: IDrizzleAdapterOptions<TSchema>) {
|
|
105
|
+
if (options) {
|
|
106
|
+
this.initialize(options);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Initialize the adapter with options
|
|
112
|
+
*/
|
|
113
|
+
public initialize(options: IDrizzleAdapterOptions<TSchema>): void {
|
|
114
|
+
if (!options.db) {
|
|
115
|
+
throw new DrizzleAdapterError('Drizzle db instance is required');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!options.schema) {
|
|
119
|
+
throw new DrizzleAdapterError('Schema definition is required');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.db = options.db;
|
|
123
|
+
this.schema = options.schema;
|
|
124
|
+
this.translator = new DrizzleTranslator({
|
|
125
|
+
normalizeFieldNames: options.normalizeFieldNames,
|
|
126
|
+
fieldMappings: options.fieldMappings,
|
|
127
|
+
schema: options.schema as unknown as Record<
|
|
128
|
+
string,
|
|
129
|
+
Record<string, unknown>
|
|
130
|
+
>
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
this.initialized = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Execute a QueryKit expression using Drizzle ORM
|
|
138
|
+
*/
|
|
139
|
+
public async execute<TResult = unknown>(
|
|
140
|
+
tableName: string,
|
|
141
|
+
expression: QueryExpression,
|
|
142
|
+
options?: IDrizzleQueryExecutionOptions
|
|
143
|
+
): Promise<TResult[]> {
|
|
144
|
+
this.ensureInitialized();
|
|
145
|
+
|
|
146
|
+
const table = this.getTable(tableName);
|
|
147
|
+
|
|
148
|
+
if (!table) {
|
|
149
|
+
throw new DrizzleAdapterError(`Table ${tableName} not found in schema`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// Start with a base query
|
|
154
|
+
let query = (this.db as IDrizzleDatabase).select().from(table);
|
|
155
|
+
|
|
156
|
+
// Add where condition if expression is provided
|
|
157
|
+
if (expression) {
|
|
158
|
+
const condition = this.translator.translate(expression);
|
|
159
|
+
query = query.where(condition);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add ordering if specified
|
|
163
|
+
if (options?.orderBy) {
|
|
164
|
+
const orderClauses: SQL[] = [];
|
|
165
|
+
|
|
166
|
+
Object.entries(options.orderBy).forEach(([field, direction]) => {
|
|
167
|
+
// Try to find the field in the schema
|
|
168
|
+
const schemaField = this.getSchemaField(tableName, field);
|
|
169
|
+
|
|
170
|
+
if (schemaField) {
|
|
171
|
+
// If field exists in schema, use it directly
|
|
172
|
+
orderClauses.push(
|
|
173
|
+
direction === 'asc' ? asc(schemaField) : desc(schemaField)
|
|
174
|
+
);
|
|
175
|
+
} else {
|
|
176
|
+
// Otherwise use raw SQL
|
|
177
|
+
orderClauses.push(
|
|
178
|
+
sql`${sql.identifier(field)} ${sql.raw(direction.toUpperCase())}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (orderClauses.length > 0) {
|
|
184
|
+
query = query.orderBy(...orderClauses);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add limit if specified
|
|
189
|
+
if (options?.limit !== undefined) {
|
|
190
|
+
query = query.limit(options.limit);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add offset if specified
|
|
194
|
+
if (options?.offset !== undefined) {
|
|
195
|
+
query = query.offset(options.offset);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Execute the query
|
|
199
|
+
const result = await query;
|
|
200
|
+
return result as TResult[];
|
|
201
|
+
} catch (error) {
|
|
202
|
+
throw new DrizzleAdapterError(
|
|
203
|
+
`Failed to execute query: ${error instanceof Error ? error.message : String(error)}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check if an expression can be executed by this adapter
|
|
210
|
+
*/
|
|
211
|
+
public canExecute(expression: QueryExpression): boolean {
|
|
212
|
+
try {
|
|
213
|
+
this.ensureInitialized();
|
|
214
|
+
return this.translator.canTranslate(expression);
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get a table from the schema
|
|
222
|
+
*/
|
|
223
|
+
private getTable(tableName: string): unknown {
|
|
224
|
+
return (this.schema as Record<string, unknown>)[tableName as string];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get a field from the schema
|
|
229
|
+
*/
|
|
230
|
+
private getSchemaField(
|
|
231
|
+
tableName: string,
|
|
232
|
+
fieldName: string
|
|
233
|
+
): SQLWrapper | null {
|
|
234
|
+
const schemaAsColumns = this.schema as unknown as Record<
|
|
235
|
+
string,
|
|
236
|
+
Record<string, SQLWrapper>
|
|
237
|
+
>;
|
|
238
|
+
const table = schemaAsColumns[tableName];
|
|
239
|
+
if (table && fieldName in table) {
|
|
240
|
+
return table[fieldName];
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Ensure the adapter is initialized
|
|
247
|
+
*/
|
|
248
|
+
private ensureInitialized(): void {
|
|
249
|
+
if (!this.initialized) {
|
|
250
|
+
throw new DrizzleAdapterError('Adapter has not been initialized');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Convenience factory to create a pre-initialized Drizzle adapter
|
|
257
|
+
*/
|
|
258
|
+
export function drizzleAdapter<TSchema extends Record<string, unknown>>(
|
|
259
|
+
options: IDrizzleAdapterOptions<TSchema>
|
|
260
|
+
): DrizzleAdapter<TSchema> {
|
|
261
|
+
return new DrizzleAdapter<TSchema>(options);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Helper types and factory for zero-cast DX with Drizzle tables
|
|
265
|
+
export type RowTypeFromDrizzleTable<TTable> = TTable extends {
|
|
266
|
+
$inferSelect: infer R;
|
|
267
|
+
}
|
|
268
|
+
? R
|
|
269
|
+
: unknown;
|
|
270
|
+
|
|
271
|
+
export type RowMapFromDrizzleSchema<TSchema extends Record<string, unknown>> = {
|
|
272
|
+
[K in keyof TSchema]: RowTypeFromDrizzleTable<TSchema[K]>;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export function createDrizzleQueryKit<
|
|
276
|
+
TSchema extends Record<string, object>
|
|
277
|
+
>(args: {
|
|
278
|
+
db: unknown;
|
|
279
|
+
schema: TSchema;
|
|
280
|
+
normalizeFieldNames?: boolean;
|
|
281
|
+
fieldMappings?: Record<string, string>;
|
|
282
|
+
security?: import('../../security').ISecurityOptions;
|
|
283
|
+
}): QueryKit<TSchema, RowMapFromDrizzleSchema<TSchema>> {
|
|
284
|
+
const adapter = new DrizzleAdapter<TSchema>();
|
|
285
|
+
adapter.initialize({
|
|
286
|
+
db: args.db,
|
|
287
|
+
schema: args.schema,
|
|
288
|
+
normalizeFieldNames: args.normalizeFieldNames,
|
|
289
|
+
fieldMappings: args.fieldMappings
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
type RowMap = RowMapFromDrizzleSchema<TSchema>;
|
|
293
|
+
|
|
294
|
+
return createQueryKit<TSchema, RowMap>({
|
|
295
|
+
adapter,
|
|
296
|
+
schema: args.schema as unknown as TSchema,
|
|
297
|
+
security: args.security
|
|
298
|
+
});
|
|
299
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryKit Adapter Types
|
|
3
|
+
*
|
|
4
|
+
* These are the core interfaces for adapters, which connect QueryKit to
|
|
5
|
+
* external systems or libraries like Drizzle ORM.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { QueryExpression } from '../parser/types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for configuring an adapter
|
|
12
|
+
*/
|
|
13
|
+
export interface IAdapterOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Schema information for type safety and validation
|
|
16
|
+
*/
|
|
17
|
+
schema?: Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Field mappings from QueryKit fields to target database fields
|
|
21
|
+
*/
|
|
22
|
+
fieldMappings?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options for a query execution
|
|
27
|
+
*/
|
|
28
|
+
export interface IQueryExecutionOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Optional transaction object
|
|
31
|
+
*/
|
|
32
|
+
transaction?: unknown;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Additional parameters specific to the adapter
|
|
36
|
+
*/
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Interface for a query adapter
|
|
42
|
+
*/
|
|
43
|
+
export interface IAdapter<TOptions extends IAdapterOptions = IAdapterOptions> {
|
|
44
|
+
/**
|
|
45
|
+
* Initialize the adapter with options
|
|
46
|
+
*
|
|
47
|
+
* @param options Adapter-specific options
|
|
48
|
+
*/
|
|
49
|
+
initialize(options: TOptions): void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Execute a QueryKit expression and return results
|
|
53
|
+
*
|
|
54
|
+
* @param tableName The table/collection name to query
|
|
55
|
+
* @param expression The QueryKit expression to execute
|
|
56
|
+
* @param options Optional execution options
|
|
57
|
+
* @returns The query results
|
|
58
|
+
*/
|
|
59
|
+
execute<T = unknown>(
|
|
60
|
+
tableName: string,
|
|
61
|
+
expression: QueryExpression,
|
|
62
|
+
options?: IQueryExecutionOptions
|
|
63
|
+
): Promise<T[]>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if an expression can be executed by this adapter
|
|
67
|
+
*
|
|
68
|
+
* @param expression The QueryKit expression to check
|
|
69
|
+
* @returns true if the expression can be executed, false otherwise
|
|
70
|
+
*/
|
|
71
|
+
canExecute(expression: QueryExpression): boolean;
|
|
72
|
+
}
|