├── .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 |
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 |
--------------------------------------------------------------------------------
/src/assets/switch-theme-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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