├── .firebaserc ├── public ├── favicon.ico ├── test.html └── playground.js ├── vitest.config.ts ├── src ├── lib │ ├── utility │ │ ├── dynamic-import.ts │ │ ├── clipboard-utils.ts │ │ ├── string-utils.test.ts │ │ ├── typescript-utils.test.ts │ │ ├── string-utils.ts │ │ ├── dom-utils.ts │ │ ├── toast-utils.ts │ │ ├── gtag-utils.ts │ │ ├── typescript-utils.ts │ │ ├── hotkey-utils.ts │ │ ├── validate-utils.ts │ │ ├── settings-utils.ts │ │ ├── logger.ts │ │ ├── async-utils.ts │ │ ├── monaco-utils.ts │ │ ├── jsdelivr-utils.ts │ │ ├── playground-utils.ts │ │ ├── css-utils.ts │ │ └── http-utils.ts │ ├── state │ │ ├── state.ts │ │ ├── firestore-state-repository.ts │ │ └── state-manager.ts │ ├── format │ │ ├── highlighter.ts │ │ └── formatter.ts │ ├── kysely │ │ ├── kysely-module.ts │ │ └── kysely-manager.ts │ ├── executer │ │ └── executer.ts │ └── constants.ts ├── vite-env.d.ts ├── main.ts ├── assets │ ├── kysely.svg │ ├── mobile.svg │ ├── mobile-white.svg │ ├── switch-theme.svg │ ├── switch-theme-white.svg │ ├── github-mark-white.svg │ ├── github-mark.svg │ ├── open-in-new-tab.svg │ ├── open-in-new-tab-white.svg │ ├── reset.css │ ├── color.css │ └── style.css ├── controllers │ ├── element-controller.ts │ ├── select-controller.ts │ ├── result-controller.ts │ ├── more-popup-controller.ts │ ├── panel-container-controller.ts │ └── editor-controller.ts ├── browser-test.ts ├── public │ └── playground.ts └── bootstrap.ts ├── .prettierrc ├── tsconfig.node.json ├── tsconfig.pub.json ├── vite.config.ts ├── firebase.json ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ ├── deploy.yml │ └── deploy-preview.yml ├── package.json ├── index.html └── README.md /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "kysely-playground" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wirekang/kysely-playground/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({}); 4 | -------------------------------------------------------------------------------- /src/lib/utility/dynamic-import.ts: -------------------------------------------------------------------------------- 1 | export function dynamicImport(url: string) { 2 | return import( 3 | /* @vite-ignore */ 4 | url 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/utility/clipboard-utils.ts: -------------------------------------------------------------------------------- 1 | export class ClipboardUtils { 2 | static writeText(v: string) { 3 | return navigator.clipboard.writeText(v); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 110, 3 | "semi": true, 4 | "singleQuote": false, 5 | "arrowParens": "always", 6 | "endOfLine": "lf", 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_BRANCH: string; 5 | readonly VITE_PREVIEW?: string; 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv; 10 | } -------------------------------------------------------------------------------- /tsconfig.pub.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "skipLibCheck": true, 5 | "strict": true, 6 | "module": "ES2015", 7 | "moduleResolution": "NodeNext", 8 | "outDir": "public" 9 | }, 10 | "include": ["src/public"] 11 | } 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { logger } from "./lib/utility/logger"; 2 | import { bootstrap } from "./bootstrap"; 3 | import { CssUtils } from "./lib/utility/css-utils"; 4 | 5 | logger.info("kysely-playground"); 6 | logger.debug("env:", import.meta.env); 7 | CssUtils.initTheme(); 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /src/lib/state/state.ts: -------------------------------------------------------------------------------- 1 | export type State = { 2 | dialect: "postgres" | "mysql" | "mssql" | "sqlite"; 3 | editors: { 4 | type: string; 5 | query: string; 6 | }; 7 | hideType?: boolean; 8 | kysely?: { 9 | type: "tag" | "branch"; 10 | name: string; 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/utility/string-utils.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { StringUtils } from "./string-utils"; 3 | 4 | test("trimPrefix", () => { 5 | expect(StringUtils.trimPrefix("asdf", "b")).toBe("asdf"); 6 | expect(StringUtils.trimPrefix("asdf", "a")).toBe("sdf"); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/utility/typescript-utils.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { TypescriptUtils } from "./typescript-utils"; 3 | 4 | test("transpile", async () => { 5 | const res = await TypescriptUtils.transpile("const a:number = 3 as any"); 6 | expect(res).toBe("const a = 3;\n"); 7 | }); 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { visualizer } from "rollup-plugin-visualizer"; 3 | 4 | export default defineConfig({ 5 | plugins: [visualizer()], 6 | build: { 7 | sourcemap: false, 8 | rollupOptions: { 9 | cache: false, 10 | }, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kysely Playground Test Page 5 | 6 | 7 | 8 |

Testing some browser-specific features...

9 |

10 |     
11 |   
12 | 
13 | 


--------------------------------------------------------------------------------
/src/lib/utility/string-utils.ts:
--------------------------------------------------------------------------------
 1 | export class StringUtils {
 2 |   static trimPrefix(s: string, prefix: string) {
 3 |     if (s.startsWith(prefix)) {
 4 |       return s.slice(prefix.length);
 5 |     }
 6 |     return s;
 7 |   }
 8 | 
 9 |   static capitalize(s: string) {
10 |     return s.charAt(0).toUpperCase() + s.substring(1);
11 |   }
12 | }
13 | 


--------------------------------------------------------------------------------
/src/lib/utility/dom-utils.ts:
--------------------------------------------------------------------------------
 1 | export class DomUtils {
 2 |   static getSearchParam(name: string) {
 3 |     return new URLSearchParams(location.search).get(name);
 4 |   }
 5 | 
 6 |   static hasSearchParam(name: string) {
 7 |     return !!DomUtils.getSearchParam(name);
 8 |   }
 9 | 
10 |   static isMac() {
11 |     return navigator.userAgent.toLowerCase().includes("mac");
12 |   }
13 | }
14 | 


--------------------------------------------------------------------------------
/src/lib/utility/toast-utils.ts:
--------------------------------------------------------------------------------
 1 | // @ts-ignore
 2 | import toastify from "toastify-js";
 3 | 
 4 | export class ToastUtils {
 5 |   static show(level: "trace" | "info" | "error", text: string) {
 6 |     toastify({
 7 |       text,
 8 |       duration: 400 + text.length * 100,
 9 |       newWindow: true,
10 |       position: "center",
11 |       gravity: "bottom",
12 |     }).showToast();
13 |   }
14 | }
15 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | # Logs
 2 | logs
 3 | *.log
 4 | npm-debug.log*
 5 | yarn-debug.log*
 6 | yarn-error.log*
 7 | pnpm-debug.log*
 8 | lerna-debug.log*
 9 | 
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 | 
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | 
26 | .firebase/*
27 | stats.html
28 | 


--------------------------------------------------------------------------------
/src/lib/utility/gtag-utils.ts:
--------------------------------------------------------------------------------
 1 | export class GtagUtils {
 2 |   static init(params: any) {
 3 |     const w = window as any;
 4 |     w.dataLayer = w.dataLayer || [];
 5 |     w.gtag = function () {
 6 |       w.dataLayer.push(arguments);
 7 |     };
 8 |     w.gtag("js", new Date());
 9 |     w.gtag("config", "G-G1QNWZ9NSP", { ...params });
10 |   }
11 | 
12 |   static event(name: string, params: any = {}) {
13 |     // @ts-ignore
14 |     window.gtag("event", name, params);
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/lib/utility/typescript-utils.ts:
--------------------------------------------------------------------------------
 1 | import { logger } from "./logger";
 2 | 
 3 | export class TypescriptUtils {
 4 |   static async transpile(input: string): Promise {
 5 |     logger.debug("transpile:\n", input);
 6 |     const ts = await import("typescript");
 7 |     return ts.transpile(input, {
 8 |       strict: false,
 9 |       skipLibCheck: true,
10 |       noImplicitAny: false,
11 |       target: ts.ScriptTarget.ES2020,
12 |       module: ts.ModuleKind.ES2020,
13 |     });
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/src/lib/format/highlighter.ts:
--------------------------------------------------------------------------------
 1 | export class Hightlighter {
 2 |   static async init() {
 3 |     const { default: hljs } = await import("highlight.js/lib/core");
 4 |     const { default: hljsSql } = await import("highlight.js/lib/languages/sql");
 5 |     const { default: hljsPlaintext } = await import("highlight.js/lib/languages/plaintext");
 6 |     hljs.registerLanguage("sql", hljsSql);
 7 |     hljs.registerLanguage("plaintext", hljsPlaintext);
 8 |     return new Hightlighter(hljs);
 9 |   }
10 | 
11 |   private constructor(private readonly hljs: any) {}
12 | 
13 |   highlight() {
14 |     this.hljs.highlightAll();
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/assets/kysely.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 
 6 | 
 7 | 
 8 | 
 9 | 
10 | 
11 | 
12 | 
13 | 


--------------------------------------------------------------------------------
/src/controllers/element-controller.ts:
--------------------------------------------------------------------------------
 1 | export class ElementController {
 2 |   constructor(readonly element: HTMLElement) {}
 3 | 
 4 |   onClick(cb: () => unknown) {
 5 |     this.element.addEventListener("click", cb);
 6 |   }
 7 | 
 8 |   setOpacity(o: string) {
 9 |     this.element.style.opacity = o;
10 |   }
11 | 
12 |   remove() {
13 |     this.element.remove();
14 |   }
15 | 
16 |   setHidden(v: boolean) {
17 |     if (v) {
18 |       this.element.setAttribute("hidden", "");
19 |     } else {
20 |       this.element.removeAttribute("hidden");
21 |     }
22 |   }
23 | 
24 |   isHidden() {
25 |     return this.element.hasAttribute("hidden");
26 |   }
27 | }
28 | 


--------------------------------------------------------------------------------
/src/lib/utility/hotkey-utils.ts:
--------------------------------------------------------------------------------
 1 | export class HotkeyUtils {
 2 |   static register(mods: Array<"ctrl" | "shift" | "alt">, key: string, cb: () => unknown) {
 3 |     const ctrl = mods.includes("ctrl");
 4 |     const shift = mods.includes("shift");
 5 |     const alt = mods.includes("alt");
 6 |     window.addEventListener("keydown", (e) => {
 7 |       if (
 8 |         e.key.toLowerCase() === key.toLowerCase() &&
 9 |         (ctrl === e.ctrlKey || ctrl === e.metaKey) &&
10 |         shift === e.shiftKey &&
11 |         alt === e.altKey
12 |       ) {
13 |         e.preventDefault();
14 |         e.stopPropagation();
15 |         cb();
16 |       }
17 |     });
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ESNext",
 4 |     "useDefineForClassFields": true,
 5 |     "lib": ["DOM", "DOM.Iterable", "ESNext"],
 6 |     "allowJs": false,
 7 |     "skipLibCheck": true,
 8 |     "esModuleInterop": false,
 9 |     "allowSyntheticDefaultImports": true,
10 |     "strict": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "module": "ESNext",
13 |     "moduleResolution": "Node",
14 |     "resolveJsonModule": true,
15 |     "isolatedModules": true,
16 |     "noEmit": true,
17 |     "noImplicitAny": true,
18 |     "strictNullChecks": true
19 |   },
20 |   "include": ["src"],
21 |   "references": [{ "path": "./tsconfig.node.json" }]
22 | }
23 | 


--------------------------------------------------------------------------------
/src/assets/mobile.svg:
--------------------------------------------------------------------------------
1 | 


--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
 1 | name: deploy
 2 | on:
 3 |   push:
 4 |     branches:
 5 |       - main
 6 |   workflow_dispatch:
 7 | jobs:
 8 |   build-and-deploy:
 9 |     runs-on: ubuntu-latest
10 |     steps:
11 |       - uses: actions/checkout@v4
12 |       - uses: actions/setup-node@v4
13 |         with:
14 |           node-version: 20
15 |           cache: npm
16 |       - run: npm ci
17 |       - run: npm run test
18 |       - run: npm run build
19 |         env:
20 |           VITE_BRANCH: ${{ github.ref_name }}
21 |       - uses: FirebaseExtended/action-hosting-deploy@v0
22 |         with:
23 |           repoToken: '${{ secrets.GITHUB_TOKEN }}'
24 |           firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_KYSELY_PLAYGROUND }}'
25 |           channelId: live
26 |           projectId: kysely-playground
27 | 


--------------------------------------------------------------------------------
/src/assets/mobile-white.svg:
--------------------------------------------------------------------------------
1 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "kysely-playground",
 3 |   "private": true,
 4 |   "type": "module",
 5 |   "scripts": {
 6 |     "dev": "vite",
 7 |     "build": "npm run build:public && tsc && vite build",
 8 |     "build:public": "tsc -p tsconfig.pub.json",
 9 |     "test": "vitest"
10 |   },
11 |   "dependencies": {
12 |     "firebase-firestore-lite": "^1.0.3",
13 |     "highlight.js": "^11.9.0",
14 |     "lz-string": "^1.5.0",
15 |     "monaco-editor": "^0.50.0-dev-20240525",
16 |     "prettier": "^3.2.4",
17 |     "sql-formatter": "^15.1.3",
18 |     "toastify-js": "^1.12.0",
19 |     "typescript": "5.0.2"
20 |   },
21 |   "devDependencies": {
22 |     "@types/node": "^18.15.12",
23 |     "kysely-0.27.2": "npm:kysely@0.27.2",
24 |     "rollup-plugin-visualizer": "^5.12.0",
25 |     "vite": "^5.0.12",
26 |     "vitest": "^1.2.1"
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/src/assets/switch-theme.svg:
--------------------------------------------------------------------------------
1 | 
4 |     
6 | 


--------------------------------------------------------------------------------
/src/assets/switch-theme-white.svg:
--------------------------------------------------------------------------------
1 | 
4 |     
6 | 


--------------------------------------------------------------------------------
/src/lib/utility/validate-utils.ts:
--------------------------------------------------------------------------------
 1 | export class ValidateUtils {
 2 |   static typeEqual(v: unknown, type: string, name: string) {
 3 |     ValidateUtils.equal(typeof v, type, `typeof ${name}`);
 4 |   }
 5 | 
 6 |   static typeIncludes(v: unknown, types: Array, name: string) {
 7 |     ValidateUtils.includes(typeof v, types, `typeof ${name}`);
 8 |   }
 9 | 
10 |   static includes(element: unknown, coll: Array, message: string) {
11 |     if (!coll.includes(element)) {
12 |       throw new ValidateError(`${message}\nexpected(includes): [${coll.join(", ")}]\nactual:  ${element}`);
13 |     }
14 |   }
15 | 
16 |   static equal(actual: unknown, expected: unknown, message: string) {
17 |     if (actual !== expected) {
18 |       throw new ValidateError(`${message}\nexpected: ${expected}\nactual:   ${actual}`);
19 |     }
20 |   }
21 | }
22 | 
23 | class ValidateError extends Error {}
24 | 


--------------------------------------------------------------------------------
/src/lib/utility/settings-utils.ts:
--------------------------------------------------------------------------------
 1 | import { LOCALSTORAGE_SETTINGS, SETTING_DEFAULTS, SETTING_KEYS } from "../constants";
 2 | 
 3 | const cache: Map = new Map();
 4 | 
 5 | export class SettingsUtils {
 6 |   static get(settingKey: (typeof SETTING_KEYS)[number]): boolean {
 7 |     const key = LOCALSTORAGE_SETTINGS + settingKey;
 8 |     if (cache.has(key)) {
 9 |       return cache.get(key) as boolean;
10 |     }
11 |     const item = localStorage.getItem(key);
12 |     let value = item === "1";
13 |     if (item === null) {
14 |       value = SETTING_DEFAULTS[settingKey];
15 |     }
16 |     cache.set(key, value);
17 |     return value;
18 |   }
19 | 
20 |   static set(settingKey: (typeof SETTING_KEYS)[number], value: boolean) {
21 |     const key = LOCALSTORAGE_SETTINGS + settingKey;
22 |     localStorage.setItem(key, value ? "1" : "0");
23 |     cache.set(key, value);
24 |   }
25 | }
26 | 


--------------------------------------------------------------------------------
/.github/workflows/deploy-preview.yml:
--------------------------------------------------------------------------------
 1 | name: deploy-preview
 2 | on:
 3 |   pull_request:
 4 |     branches:
 5 |       - main
 6 |   workflow_dispatch:
 7 | jobs:
 8 |   build-and-deploy:
 9 |     runs-on: ubuntu-latest
10 |     steps:
11 |       - uses: actions/checkout@v4
12 |       - uses: actions/setup-node@v4
13 |         with:
14 |           node-version: 20
15 |           cache: npm
16 |       - run: npm ci
17 |       - run: npm run test
18 |       - run: npm run build
19 |         env:
20 |           VITE_BRANCH: ${{ github.ref_name }}
21 |           VITE_PREVIEW: 1
22 |       - uses: FirebaseExtended/action-hosting-deploy@v0
23 |         with:
24 |           repoToken: "${{ secrets.GITHUB_TOKEN }}"
25 |           firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_KYSELY_PLAYGROUND }}"
26 |           channelId: preview
27 |           expires: 7d
28 |           projectId: kysely-playground
29 | 


--------------------------------------------------------------------------------
/src/assets/github-mark-white.svg:
--------------------------------------------------------------------------------
1 | 


--------------------------------------------------------------------------------
/src/assets/github-mark.svg:
--------------------------------------------------------------------------------
1 | 


--------------------------------------------------------------------------------
/src/browser-test.ts:
--------------------------------------------------------------------------------
 1 | import { Executer } from "./lib/executer/executer";
 2 | import { logger } from "./lib/utility/logger";
 3 | import { HotkeyUtils } from "./lib/utility/hotkey-utils";
 4 | 
 5 | function testHotKeyUtils() {
 6 |   HotkeyUtils.register(["ctrl"], "s", () => {
 7 |     logger.info("ctrl-s");
 8 |   });
 9 |   HotkeyUtils.register(["ctrl", "shift"], "s", () => {
10 |     logger.info("ctrl-shift-s");
11 |   });
12 | }
13 | 
14 | async function testExecuter() {
15 |   const e = new Executer({
16 |     "is-number": "https://esm.run/is-number@7.0.0",
17 |     "is-odd": "https://esm.run/is-odd@latest",
18 |   });
19 |   await e.execute(`
20 |   import isNumber from "is-number";
21 |   import isOdd from 'is-odd';
22 | 
23 |   await new Promise(
24 |     (resolve)=>{
25 |       setTimeout(resolve,1000)
26 |     }
27 |   );
28 |   console.log("is 1234 number:",isNumber(1234))
29 |   console.log("is 1234 odd:",isOdd(1234))
30 |   `);
31 | }
32 | 
33 | testHotKeyUtils();
34 | testExecuter();
35 | 


--------------------------------------------------------------------------------
/src/lib/utility/logger.ts:
--------------------------------------------------------------------------------
 1 | import { DEBUG } from "../constants";
 2 | 
 3 | export const logger = {
 4 |   debug: (...messages: Array) => {
 5 |     log(0, messages);
 6 |   },
 7 |   info: (...messages: Array) => {
 8 |     log(1, messages);
 9 |   },
10 |   warn: (...messages: Array) => {
11 |     log(2, messages);
12 |   },
13 |   error: (...messages: Array) => {
14 |     log(3, messages);
15 |   },
16 | };
17 | 
18 | const MIN_LEVEL = (() => {
19 |   if (DEBUG) {
20 |     return 0;
21 |   }
22 |   return import.meta.env.DEV || import.meta.env.VITE_PREVIEW ? 0 : 1;
23 | })();
24 | const PREFIXES = ["DBG", "INF", "WRN", "ERR"];
25 | const PREFIX_COLORS = ["#bbb", "#fff", "#fb6", "#f66"];
26 | 
27 | function log(level: number, messages: Array) {
28 |   if (level < MIN_LEVEL) {
29 |     return;
30 |   }
31 |   console.log(
32 |     `%c ${PREFIXES[level]} %c`,
33 |     `background-color: #000; color: ${PREFIX_COLORS[level]}`,
34 |     `backgrond-color: none; color:none;`,
35 |     ...messages,
36 |   );
37 | }
38 | 


--------------------------------------------------------------------------------
/src/controllers/select-controller.ts:
--------------------------------------------------------------------------------
 1 | export class SelectController {
 2 |   private options: Array