Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/playground-next/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Next.js
.next/
out/

# Build
build/
dist/

# Dependencies
node_modules/

# Debug
npm-debug.log*
.pnpm-debug.log*

# Local env
.env*.local
25 changes: 25 additions & 0 deletions packages/playground-next/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Polyglot SQL Playground (Next.js)

A Next.js + Turbopack playground that mirrors GrowthBook's structure. Demonstrates `@polyglot-sql/sdk` via `playground-shared` (top-level import, like GrowthBook's shared package).

## Setup

From the polyglot repo root:

```bash
pnpm install
pnpm --filter @polyglot-sql/playground-next run dev
```

Open http://localhost:3000.

## Structure (mirrors GrowthBook)

- **FormatDemo** – Loaded with `ssr: false`; imports from `playground-shared/sql` (top-level SDK import like GrowthBook)
- **playground-shared** – Wraps `@polyglot-sql/sdk`; `formatWithPolyglot` throws if format is undefined (top-level await issue)

## Configuration

- Next.js 16 with Turbopack
- Pages Router
- `transpilePackages: ["@polyglot-sql/sdk", "playground-shared"]`
104 changes: 104 additions & 0 deletions packages/playground-next/components/FormatDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
initPolyglotFormat,
formatWithPolyglot,
} from "playground-shared/sql";
import { useState, useEffect, useCallback } from "react";

const DEFAULT_SQL = `select u.id,u.name,u.email,count(o.id) as order_count from users u left join orders o on u.id=o.user_id where u.active=true group by u.id,u.name,u.email limit 50;`;

export default function FormatDemo() {
const [sql, setSql] = useState(DEFAULT_SQL);
const [output, setOutput] = useState("");
const [initDone, setInitDone] = useState(false);

useEffect(() => {
initPolyglotFormat().then(() => setInitDone(true));
}, []);

const handleFormat = useCallback(() => {
setOutput("");
try {
const result = formatWithPolyglot(sql, "postgresql");
setOutput(result ?? "");
} catch (e) {
setOutput(String(e));
}
}, [sql]);

return (
<div
style={{
maxWidth: 800,
margin: "0 auto",
padding: 24,
fontFamily: "system-ui",
}}
>
<h1 style={{ marginBottom: 24 }}>Polyglot SQL Playground</h1>

<div style={{ marginBottom: 16 }}>
<textarea
value={sql}
onChange={(e) => setSql(e.target.value)}
style={{
width: "100%",
minHeight: 120,
padding: 12,
fontFamily: "ui-monospace, monospace",
fontSize: 13,
color: "#18181b",
background: "#fff",
border: "1px solid #e4e4e7",
borderRadius: 6,
resize: "vertical",
}}
spellCheck={false}
/>
</div>

<button
onClick={handleFormat}
disabled={!initDone}
style={{
padding: "8px 16px",
background: initDone ? "#18181b" : "#d4d4d8",
color: "white",
border: "none",
borderRadius: 6,
cursor: initDone ? "pointer" : "not-allowed",
fontWeight: 500,
marginBottom: 16,
}}
>
Format
</button>

<div
style={{
fontSize: 12,
color: "#71717a",
marginBottom: 8,
}}
>
Output
</div>
<div
style={{
width: "100%",
minHeight: 120,
padding: 12,
fontFamily: "ui-monospace, monospace",
fontSize: 13,
border: "1px solid #e4e4e7",
borderRadius: 6,
background: "#fafafa",
color: "#18181b",
whiteSpace: "pre-wrap",
wordBreak: "break-all",
}}
>
{output}
</div>
</div>
);
}
6 changes: 6 additions & 0 deletions packages/playground-next/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
15 changes: 15 additions & 0 deletions packages/playground-next/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const path = require("path");

/** @type {import('next').NextConfig} */
const nextConfig = {
turbopack: {
root: path.join(__dirname, "../.."),
},
experimental: {
turbopackFileSystemCacheForDev: true,
turbopackFileSystemCacheForBuild: true,
},
transpilePackages: ["@polyglot-sql/sdk", "playground-shared"],
};

module.exports = nextConfig;
23 changes: 23 additions & 0 deletions packages/playground-next/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@polyglot-sql/playground-next",
"version": "0.1.11",
"private": true,
"scripts": {
"dev": "concurrently \"pnpm --filter playground-shared run dev\" \"next dev\"",
"build": "next build",
"start": "next start"
},
"dependencies": {
"playground-shared": "workspace:*",
"next": "^16.1.6",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"concurrently": "^9.1.0",
"@types/node": "^22.8.6",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"typescript": "^5.7.0"
}
}
6 changes: 6 additions & 0 deletions packages/playground-next/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { AppProps } from "next/app";
import "../styles/globals.css";

export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
11 changes: 11 additions & 0 deletions packages/playground-next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import dynamic from "next/dynamic";
import type { ComponentType } from "react";

const FormatDemo = dynamic(
() => import("../components/FormatDemo"),
{ ssr: false }
) as ComponentType;

export default function Home() {
return <FormatDemo />;
}
23 changes: 23 additions & 0 deletions packages/playground-next/styles/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
* {
box-sizing: border-box;
}

body {
margin: 0;
font-family: ui-sans-serif, system-ui, sans-serif;
background: #fafafa;
color: #18181b;
}

@media (prefers-color-scheme: dark) {
body {
background: #09090b;
color: #fafafa;
}
}

@keyframes spin {
to {
transform: rotate(360deg);
}
}
40 changes: 40 additions & 0 deletions packages/playground-next/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
29 changes: 29 additions & 0 deletions packages/playground-shared/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "playground-shared",
"version": "0.0.1",
"private": true,
"main": "dist/index.js",
"types": "src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"default": "./dist/index.js"
},
"./sql": {
"types": "./src/sql.ts",
"default": "./dist/sql.js"
}
},
"scripts": {
"build": "rimraf dist && tsc",
"dev": "tsc --watch",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@polyglot-sql/sdk": "^0.1.11"
},
"devDependencies": {
"rimraf": "^3.0.2",
"typescript": "^5.7.0"
}
}
1 change: 1 addition & 0 deletions packages/playground-shared/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as sql from "./sql";
46 changes: 46 additions & 0 deletions packages/playground-shared/src/sql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Mirrors GrowthBook's shared/sql.ts - top-level import from @polyglot-sql/sdk.
* When this module is loaded during SSR, the SDK loads on the server → WASM URL error.
*/
import {
format as polyglotFormat,
init as polyglotInit,
Dialect,
} from "@polyglot-sql/sdk";
import * as polyglot from "@polyglot-sql/sdk";

let polyglotInitPromise: Promise<void> | null = null;

export async function initPolyglotFormat(): Promise<void> {
if (typeof polyglotInit !== "function") return;
if (!polyglotInitPromise) {
polyglotInitPromise = polyglotInit().catch(() => {});
}
await polyglotInitPromise;
}

export function formatWithPolyglot(
sql: string,
dialect: string,
): string | null {
if (typeof polyglotFormat === "undefined") {
throw new Error(
"polyglotFormat (format) is undefined. polyglot is not a module but: " +
JSON.stringify(polyglot),
);
}
const dialectMap: Record<string, Dialect> = {
mysql: Dialect.MySQL,
postgresql: Dialect.PostgreSQL,
bigquery: Dialect.BigQuery,
snowflake: Dialect.Snowflake,
};
const pgDialect = dialectMap[dialect] ?? Dialect.PostgreSQL;
const result = polyglotFormat(sql, pgDialect);
if (result?.success && result?.sql?.length) {
return result.sql[0];
}
return null;
}

export { polyglot, Dialect };
17 changes: 17 additions & 0 deletions packages/playground-shared/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"target": "es2020",
"skipLibCheck": true,
"strict": true,
"moduleResolution": "node",
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"]
}
Loading