The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .dockerignore
├── .eslintrc.json
├── .gitattributes
├── .github
    └── workflows
    │   └── check.yaml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
    └── settings.json
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── build-dialect.js
├── components.json
├── docs
    ├── README.md
    ├── sidebar.md
    └── window-tab.md
├── jest.config.ts
├── jest.setup.ts
├── next.config.js
├── open-next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── public
    ├── assets
    │   ├── clouds.jpg
    │   ├── login-planet.png
    │   ├── login-portal.png
    │   ├── login-stars.png
    │   ├── onboarding_dark.svg
    │   ├── reset-password-orb.webp
    │   └── sat.png
    ├── caret.svg
    ├── cloudflare.png
    ├── data-editor.png
    ├── edit-table.png
    ├── extension
    │   ├── chart-dark.png
    │   ├── chart-light.png
    │   ├── definition-dark.png
    │   ├── definition-light.png
    │   ├── term-dark.png
    │   └── term-light.png
    ├── google.png
    ├── hero-banner.jpg
    ├── icons
    │   ├── outerbase.ico
    │   ├── outerbase.png
    │   └── outerbase.svg
    ├── logo.svg
    ├── next.svg
    ├── open-source.png
    ├── outerbase-banner.jpg
    ├── rqlite.png
    ├── screenshot.png
    ├── screenshot2.png
    ├── social
    │   ├── github.svg
    │   └── twitter.svg
    ├── sql-editor.png
    ├── sqlite-icon.svg
    ├── sqljs
    │   ├── sql-wasm.js
    │   └── sql-wasm.wasm
    ├── turso.jpeg
    ├── turso.png
    ├── valtown.svg
    └── vercel.svg
├── src
    ├── app
    │   ├── (dark-only)
    │   │   ├── layout.tsx
    │   │   ├── new-workspace
    │   │   │   └── page.tsx
    │   │   ├── password_reset
    │   │   │   └── page.tsx
    │   │   ├── signin
    │   │   │   ├── page.tsx
    │   │   │   ├── starbase-portal.tsx
    │   │   │   └── styles.css
    │   │   ├── signup-workspace
    │   │   │   └── page.tsx
    │   │   ├── signup
    │   │   │   ├── page.tsx
    │   │   │   ├── signup-form.tsx
    │   │   │   └── signup-otp.tsx
    │   │   ├── spaceship-container.tsx
    │   │   └── verify
    │   │   │   └── page.tsx
    │   ├── (outerbase)
    │   │   ├── account
    │   │   │   ├── account-footer.tsx
    │   │   │   ├── editor-theme.tsx
    │   │   │   ├── page.tsx
    │   │   │   ├── two-factor-auth.tsx
    │   │   │   ├── user-avatar.tsx
    │   │   │   └── user-form-input.tsx
    │   │   ├── auth-provider.tsx
    │   │   ├── base-template.tsx
    │   │   ├── layout.tsx
    │   │   ├── local-setting-dialog.tsx
    │   │   ├── local
    │   │   │   ├── board
    │   │   │   │   └── [boardId]
    │   │   │   │   │   └── page.tsx
    │   │   │   ├── dialog-base-delete.tsx
    │   │   │   ├── dialog-board-create.tsx
    │   │   │   ├── dialog-board-delete.tsx
    │   │   │   ├── edit-base
    │   │   │   │   └── [baseId]
    │   │   │   │   │   └── page.tsx
    │   │   │   ├── hooks.tsx
    │   │   │   ├── new-base
    │   │   │   │   ├── [driver]
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── cloud-support-only.tsx
    │   │   │   │   ├── durable-object
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── mysql
    │   │   │   │   │   └── page.tsx
    │   │   │   │   └── postgres
    │   │   │   │   │   └── page.tsx
    │   │   │   └── page.tsx
    │   │   ├── nav-board-layout.tsx
    │   │   ├── nav-header-local.tsx
    │   │   ├── nav-header.tsx
    │   │   ├── nav-layout.tsx
    │   │   ├── nav-profile.tsx
    │   │   ├── nav-signin-banner.tsx
    │   │   ├── new-resource-button.tsx
    │   │   ├── new-resource-list.tsx
    │   │   ├── page.tsx
    │   │   ├── resource-card-loading.tsx
    │   │   ├── resource-item-helper.tsx
    │   │   ├── session-provider.tsx
    │   │   ├── signout
    │   │   │   └── page.tsx
    │   │   ├── w
    │   │   │   └── [workspaceId]
    │   │   │   │   ├── [baseId]
    │   │   │   │       └── page.tsx
    │   │   │   │   ├── billing
    │   │   │   │       └── page.tsx
    │   │   │   │   ├── board
    │   │   │   │       └── [boardId]
    │   │   │   │       │   └── page.tsx
    │   │   │   │   ├── dialog-base-delete.tsx
    │   │   │   │   ├── dialog-board-create.tsx
    │   │   │   │   ├── dialog-board-delete.tsx
    │   │   │   │   ├── edit-base
    │   │   │   │       ├── [baseId]
    │   │   │   │       │   └── page.tsx
    │   │   │   │       └── page.tsx
    │   │   │   │   ├── layout.tsx
    │   │   │   │   ├── new-base
    │   │   │   │       ├── [driver]
    │   │   │   │       │   └── page.tsx
    │   │   │   │       └── page.tsx
    │   │   │   │   ├── page.tsx
    │   │   │   │   ├── redirect-valid-workspace.ts
    │   │   │   │   └── settings
    │   │   │   │       ├── delete.tsx
    │   │   │   │       ├── detail.tsx
    │   │   │   │       ├── gateway.tsx
    │   │   │   │       ├── members.tsx
    │   │   │   │       └── page.tsx
    │   │   └── workspace-provider.tsx
    │   ├── (public)
    │   │   ├── about
    │   │   │   └── page.tsx
    │   │   ├── databases
    │   │   │   ├── mysql
    │   │   │   │   └── page.tsx
    │   │   │   └── postgres
    │   │   │   │   └── page.tsx
    │   │   ├── docs
    │   │   │   ├── connect-turso
    │   │   │   │   └── page.mdx
    │   │   │   ├── connect-valtown
    │   │   │   │   └── page.mdx
    │   │   │   ├── embed-iframe-client
    │   │   │   │   └── page.mdx
    │   │   │   ├── layout.tsx
    │   │   │   └── page.mdx
    │   │   ├── layout.tsx
    │   │   ├── privacy
    │   │   │   └── page.tsx
    │   │   └── terms
    │   │   │   └── page.tsx
    │   ├── (theme)
    │   │   ├── client
    │   │   │   ├── layout.tsx
    │   │   │   └── s
    │   │   │   │   ├── [[...driver]]
    │   │   │   │       ├── page-client.tsx
    │   │   │   │       └── page.tsx
    │   │   │   │   └── starbase
    │   │   │   │       └── page.tsx
    │   │   ├── connect
    │   │   │   └── saved-connection-storage.ts
    │   │   ├── embed
    │   │   │   ├── [driver]
    │   │   │   │   ├── page-client.tsx
    │   │   │   │   └── page.tsx
    │   │   │   └── board
    │   │   │   │   └── [boardId]
    │   │   │   │       └── page.tsx
    │   │   ├── playground
    │   │   │   ├── client
    │   │   │   │   ├── page-client.tsx
    │   │   │   │   └── page.tsx
    │   │   │   └── mysql
    │   │   │   │   └── [roomName]
    │   │   │   │       ├── page-client.tsx
    │   │   │   │       └── page.tsx
    │   │   └── theme_layout.tsx
    │   ├── api
    │   │   └── events
    │   │   │   ├── insert-tracking-record.ts
    │   │   │   └── route.ts
    │   ├── apple-icon.png
    │   ├── codemirror-override.css
    │   ├── connect
    │   │   └── route.ts
    │   ├── favicon.ico
    │   ├── fonts
    │   │   ├── UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2
    │   │   ├── UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2
    │   │   ├── UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2
    │   │   ├── UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2
    │   │   ├── UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2
    │   │   ├── UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2
    │   │   └── UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2
    │   ├── globals.css
    │   ├── icon.png
    │   ├── layout.tsx
    │   ├── proxy
    │   │   ├── d1
    │   │   │   └── route.ts
    │   │   └── wae
    │   │   │   └── route.tsx
    │   ├── sitemap.ts
    │   ├── storybook
    │   │   ├── avatar
    │   │   │   └── page.tsx
    │   │   ├── blank_page.mdx
    │   │   ├── button
    │   │   │   ├── page.tsx
    │   │   │   └── refresh-button.tsx
    │   │   ├── chart-editor
    │   │   │   └── page.tsx
    │   │   ├── column-type
    │   │   │   └── page.tsx
    │   │   ├── common-dialog
    │   │   │   └── page.tsx
    │   │   ├── create-dialog
    │   │   │   ├── example.tsx
    │   │   │   └── page.mdx
    │   │   ├── editor-prompt-widget
    │   │   │   └── page.tsx
    │   │   ├── editor
    │   │   │   └── page.tsx
    │   │   ├── input
    │   │   │   └── page.tsx
    │   │   ├── label
    │   │   │   └── page.tsx
    │   │   ├── layout.tsx
    │   │   ├── listview
    │   │   │   ├── example1.tsx
    │   │   │   └── page.mdx
    │   │   ├── loader
    │   │   │   └── page.tsx
    │   │   ├── menu-bar
    │   │   │   └── page.tsx
    │   │   ├── page.mdx
    │   │   ├── select
    │   │   │   └── page.tsx
    │   │   ├── server-loading
    │   │   │   └── page.tsx
    │   │   ├── table
    │   │   │   └── page.tsx
    │   │   ├── toggle
    │   │   │   └── page.tsx
    │   │   └── toolbar
    │   │   │   ├── example.tsx
    │   │   │   └── page.mdx
    │   └── styles
    │   │   └── markdown.css
    ├── components
    │   ├── base-handle.tsx
    │   ├── base-node.tsx
    │   ├── board
    │   │   ├── board-auto-value.ts
    │   │   ├── board-canvas.tsx
    │   │   ├── board-chart-editor.tsx
    │   │   ├── board-chart.tsx
    │   │   ├── board-delete-dialog.tsx
    │   │   ├── board-filter-dialog.tsx
    │   │   ├── board-filter.tsx
    │   │   ├── board-filter
    │   │   │   ├── board-filter-date.tsx
    │   │   │   ├── board-filter-enum.tsx
    │   │   │   └── board-filter-input.tsx
    │   │   ├── board-provider.tsx
    │   │   ├── board-source-picker.tsx
    │   │   ├── board-sql-error-log.tsx
    │   │   ├── board-style.css
    │   │   ├── board-tool
    │   │   │   ├── board-button-menu.tsx
    │   │   │   ├── board-title-menu.tsx
    │   │   │   ├── board-tool.tsx
    │   │   │   └── board-toolbar.tsx
    │   │   └── index.tsx
    │   ├── button-group.tsx
    │   ├── chart
    │   │   ├── chart-background-image.tsx
    │   │   ├── chart-background-selection.tsx
    │   │   ├── chart-series-combobox.tsx
    │   │   ├── chart-series.tsx
    │   │   ├── chart-type-button.tsx
    │   │   ├── chart-type-selection.tsx
    │   │   ├── chart-type.ts
    │   │   ├── chart-y-axis-section.tsx
    │   │   ├── echart-options-builder.ts
    │   │   ├── edit-chart-menu.tsx
    │   │   ├── index.tsx
    │   │   ├── simple-color-picker.tsx
    │   │   └── simple-input.tsx
    │   ├── circular-progress-bar.tsx
    │   ├── client-only.tsx
    │   ├── code-block.tsx
    │   ├── common-dialog
    │   │   └── index.tsx
    │   ├── connection-config-editor
    │   │   ├── index.tsx
    │   │   └── template
    │   │   │   ├── cloudflare-wae.tsx
    │   │   │   ├── cloudflare.tsx
    │   │   │   ├── generic.tsx
    │   │   │   ├── index.ts
    │   │   │   ├── mysql.tsx
    │   │   │   ├── postgres.tsx
    │   │   │   ├── rqlite.tsx
    │   │   │   ├── sqlite.tsx
    │   │   │   ├── starbase.tsx
    │   │   │   ├── turso.tsx
    │   │   │   └── valtown.tsx
    │   ├── copyable-text.tsx
    │   ├── create-dialog.tsx
    │   ├── editor
    │   │   ├── prompt-plugin.css
    │   │   ├── prompt-plugin.tsx
    │   │   ├── prompt-widget.tsx
    │   │   └── sql-editor
    │   │   │   └── variable-highlight-plugin.ts
    │   ├── filehandler-picker.tsx
    │   ├── gui
    │   │   ├── aggregate-result
    │   │   │   └── aggregate-result-button.tsx
    │   │   ├── code-preview
    │   │   │   └── index.tsx
    │   │   ├── column-list-editor.tsx
    │   │   ├── connection-dialog.tsx
    │   │   ├── context-menu-handler.tsx
    │   │   ├── custom
    │   │   │   └── ErrorMessage.tsx
    │   │   ├── database-gui.tsx
    │   │   ├── export
    │   │   │   └── export-result-button.tsx
    │   │   ├── json-editor
    │   │   │   └── index.tsx
    │   │   ├── list-button-item.tsx
    │   │   ├── loading-opacity.tsx
    │   │   ├── logo-loading.tsx
    │   │   ├── main-connection.tsx
    │   │   ├── providers
    │   │   │   └── full-editor-provider.tsx
    │   │   ├── query-explanation-diagram
    │   │   │   ├── build-query-explanation-flow.ts
    │   │   │   ├── index.tsx
    │   │   │   └── node-type
    │   │   │   │   ├── nested-loop.tsx
    │   │   │   │   ├── operation-block.tsx
    │   │   │   │   ├── query-block.tsx
    │   │   │   │   ├── table-block.tsx
    │   │   │   │   ├── tooltip-handle.tsx
    │   │   │   │   └── union-block.tsx
    │   │   ├── query-explanation.tsx
    │   │   ├── query-progress-log.tsx
    │   │   ├── query-result-table.tsx
    │   │   ├── result-stat.tsx
    │   │   ├── save-doc-button.tsx
    │   │   ├── schema-editor
    │   │   │   ├── column-check-popup.tsx
    │   │   │   ├── column-collation.tsx
    │   │   │   ├── column-conflict-clause.tsx
    │   │   │   ├── column-default-value-input.tsx
    │   │   │   ├── column-fk-popup.tsx
    │   │   │   ├── column-generate-popup.tsx
    │   │   │   ├── column-pk-popup.tsx
    │   │   │   ├── column-provider.tsx
    │   │   │   ├── column-type-selector.tsx
    │   │   │   ├── column-unique-popup.tsx
    │   │   │   ├── index.tsx
    │   │   │   ├── schema-create
    │   │   │   │   ├── index.tsx
    │   │   │   │   └── schema-create-form.tsx
    │   │   │   ├── schema-editor-column-list.tsx
    │   │   │   ├── schema-editor-constraint-list.tsx
    │   │   │   ├── schema-name-select.tsx
    │   │   │   └── schema-save-dialog.tsx
    │   │   ├── schema-sidebar-list.tsx
    │   │   ├── schema-sidebar.tsx
    │   │   ├── sidebar-tab.tsx
    │   │   ├── sidebar
    │   │   │   ├── saved-doc-tab
    │   │   │   │   ├── create-namespace-button.tsx
    │   │   │   │   ├── index.tsx
    │   │   │   │   ├── remove-doc-dialog.tsx
    │   │   │   │   ├── remove-namespace-dialog.tsx
    │   │   │   │   └── rename-namespace-dialog.tsx
    │   │   │   └── tools-sidebar.tsx
    │   │   ├── sortable-tab.tsx
    │   │   ├── sql-editor
    │   │   │   ├── function-tooltips.ts
    │   │   │   ├── index.tsx
    │   │   │   ├── sql-tablename-highlight.ts
    │   │   │   ├── statement-highlight.test.ts
    │   │   │   ├── statement-highlight.ts
    │   │   │   └── use-editor-theme.tsx
    │   │   ├── studio.tsx
    │   │   ├── table-cell
    │   │   │   ├── big-number-cell.tsx
    │   │   │   ├── blob-cell.tsx
    │   │   │   ├── create-editable-cell.tsx
    │   │   │   ├── display-link-cell.tsx
    │   │   │   ├── generic-cell.tsx
    │   │   │   ├── number-cell.tsx
    │   │   │   ├── styles.module.css
    │   │   │   └── text-cell.tsx
    │   │   ├── table-combobox
    │   │   │   ├── TableColumnCombobox.tsx
    │   │   │   └── TableCombobox.tsx
    │   │   ├── table-optimized
    │   │   │   ├── helper.ts
    │   │   │   ├── index.tsx
    │   │   │   ├── optimize-table-state.tsx
    │   │   │   ├── table-cell.tsx
    │   │   │   ├── table-fake-body-padding.tsx
    │   │   │   ├── table-fake-row-padding.tsx
    │   │   │   ├── table-header-list.tsx
    │   │   │   ├── table-header-resize-handler.tsx
    │   │   │   ├── table-header.tsx
    │   │   │   └── use-visibility-calculation.ts
    │   │   ├── table-result
    │   │   │   ├── context-menu.tsx
    │   │   │   ├── filter-column.tsx
    │   │   │   ├── helper.ts
    │   │   │   ├── render-cell.tsx
    │   │   │   ├── table-state-actions.ts
    │   │   │   └── type.tsx
    │   │   ├── tabs-result
    │   │   │   ├── explain-result-tab.tsx
    │   │   │   └── query-result-tab.tsx
    │   │   ├── tabs
    │   │   │   ├── mass-drop-table.tsx
    │   │   │   ├── query-placeholder.tsx
    │   │   │   ├── query-tab.tsx
    │   │   │   ├── relational-diagram-tab
    │   │   │   │   ├── context-menu-diagram.tsx
    │   │   │   │   ├── database-schema-node.tsx
    │   │   │   │   ├── devtools.tsx
    │   │   │   │   ├── download-image-diagram.tsx
    │   │   │   │   ├── erd-table-column-edge.tsx
    │   │   │   │   ├── erd-table-column.tsx
    │   │   │   │   └── index.tsx
    │   │   │   ├── schema-editor-tab.tsx
    │   │   │   └── table-data-tab.tsx
    │   │   ├── toolbar.tsx
    │   │   └── windows-tab.tsx
    │   ├── hooks
    │   │   └── useElementResize.ts
    │   ├── icons
    │   │   ├── outerbase-icon.tsx
    │   │   ├── outerbase.tsx
    │   │   └── server-loading.tsx
    │   ├── label-input.tsx
    │   ├── listview
    │   │   └── index.tsx
    │   ├── mdx
    │   │   ├── docs-navigation.tsx
    │   │   └── docs.tsx
    │   ├── orbit
    │   │   ├── avatar.tsx
    │   │   ├── banner
    │   │   │   ├── index.tsx
    │   │   │   ├── pixel-filter.tsx
    │   │   │   └── ripple-filter.tsx
    │   │   ├── block.tsx
    │   │   ├── button.tsx
    │   │   ├── input.tsx
    │   │   ├── inset.tsx
    │   │   ├── label.tsx
    │   │   ├── loader.tsx
    │   │   ├── menu-bar.tsx
    │   │   ├── section.tsx
    │   │   ├── select.tsx
    │   │   └── toggle.tsx
    │   ├── page-loading.tsx
    │   ├── page-tracker.tsx
    │   ├── picker
    │   │   └── country-code-picker.tsx
    │   ├── resource-card
    │   │   ├── icon.tsx
    │   │   ├── index.tsx
    │   │   ├── utils.tsx
    │   │   └── visual.tsx
    │   ├── screen-dropzone.tsx
    │   ├── selectable-table.tsx
    │   ├── sidebar-menu.tsx
    │   ├── theme-toggle.tsx
    │   ├── ui
    │   │   ├── alert-dialog.tsx
    │   │   ├── avatar.tsx
    │   │   ├── button.tsx
    │   │   ├── card.tsx
    │   │   ├── checkbox.tsx
    │   │   ├── collapsible.tsx
    │   │   ├── command.tsx
    │   │   ├── context-menu.tsx
    │   │   ├── dialog.tsx
    │   │   ├── dropdown-menu.tsx
    │   │   ├── highlight-text.tsx
    │   │   ├── hover-card.tsx
    │   │   ├── input.tsx
    │   │   ├── label.tsx
    │   │   ├── menubar.tsx
    │   │   ├── navigation-menu.tsx
    │   │   ├── popover.tsx
    │   │   ├── radio-group.tsx
    │   │   ├── resizable.tsx
    │   │   ├── scroll-area.tsx
    │   │   ├── select.tsx
    │   │   ├── separator.tsx
    │   │   ├── sheet.tsx
    │   │   ├── sonner.tsx
    │   │   ├── table.tsx
    │   │   ├── textarea.tsx
    │   │   ├── toggle-group.tsx
    │   │   ├── toggle.tsx
    │   │   └── tooltip.tsx
    │   └── website-layout.tsx
    ├── const.ts
    ├── constants
    │   └── http-status.ts
    ├── context
    │   ├── auto-complete-provider.tsx
    │   ├── driver-provider.tsx
    │   └── schema-provider.tsx
    ├── core
    │   ├── builtin-tab
    │   │   ├── open-erd-tab.tsx
    │   │   ├── open-mass-drop-table.tsx
    │   │   ├── open-query-tab.tsx
    │   │   ├── open-schema-tab.tsx
    │   │   └── open-table-tab.tsx
    │   ├── channel-builtin.tsx
    │   ├── channel.tsx
    │   ├── command
    │   │   └── index.ts
    │   ├── extension-base.tsx
    │   ├── extension-manager.tsx
    │   ├── extension-tab.tsx
    │   ├── query-pipeline.tsx
    │   └── standard-extension.tsx
    ├── drivers
    │   ├── agent
    │   │   ├── base.ts
    │   │   ├── chatgpt.ts
    │   │   ├── cloudflare.ts
    │   │   ├── common.ts
    │   │   └── list.tsx
    │   ├── base-driver.ts
    │   ├── board-source
    │   │   ├── README
    │   │   ├── base-source.ts
    │   │   └── local.tsx
    │   ├── board-storage
    │   │   ├── base.ts
    │   │   ├── local.ts
    │   │   └── outerbase.ts
    │   ├── common-sql-imp.ts
    │   ├── database
    │   │   ├── cloudflare-d1.ts
    │   │   ├── cloudflare-wae.ts
    │   │   ├── rqlite.ts
    │   │   ├── sqljs.ts
    │   │   ├── starbasedb.ts
    │   │   ├── turso.tsx
    │   │   └── valtown.ts
    │   ├── helpers.ts
    │   ├── iframe-driver.ts
    │   ├── mysql
    │   │   ├── generate-schema.ts
    │   │   ├── mysql-data-type.ts
    │   │   ├── mysql-driver.ts
    │   │   └── mysql-playground-driver.ts
    │   ├── postgres
    │   │   ├── generate-schema.ts
    │   │   ├── postgres-data-type.ts
    │   │   └── postgres-driver.ts
    │   ├── query-builder.ts
    │   ├── saved-doc
    │   │   ├── electron-saved-doc.ts
    │   │   ├── indexdb-saved-doc.ts
    │   │   └── saved-doc-driver.ts
    │   ├── sql-helper.ts
    │   ├── sqlite-base-driver.ts
    │   └── sqlite
    │   │   ├── function-tooltip.json
    │   │   ├── functions
    │   │       ├── abs.md
    │   │       ├── avg.md
    │   │       ├── changes.md
    │   │       ├── char.md
    │   │       ├── coalesce.md
    │   │       ├── concat.md
    │   │       ├── concat_ws.md
    │   │       ├── count.md
    │   │       ├── format.md
    │   │       ├── fts5.md
    │   │       ├── glob.md
    │   │       ├── group_concat.md
    │   │       ├── hex.md
    │   │       ├── ifnull.md
    │   │       ├── iif.md
    │   │       ├── instr.md
    │   │       ├── last_insert_rowid.md
    │   │       ├── length.md
    │   │       ├── libsql_vector_idx.md
    │   │       ├── like.md
    │   │       ├── lower.md
    │   │       ├── ltrim.md
    │   │       ├── max.md
    │   │       ├── min.md
    │   │       ├── nullif.md
    │   │       ├── octet_length.md
    │   │       ├── printf.md
    │   │       ├── quote.md
    │   │       ├── random.md
    │   │       ├── randomblob.md
    │   │       ├── replace.md
    │   │       ├── round.md
    │   │       ├── rtrim.md
    │   │       ├── sign.md
    │   │       ├── string_agg.md
    │   │       ├── sum.md
    │   │       ├── total.md
    │   │       ├── vector.md
    │   │       ├── vector_distance_cos.md
    │   │       ├── vector_extract.md
    │   │       └── vector_top_k.md
    │   │   ├── sql-helper.test.ts
    │   │   ├── sql-helper.ts
    │   │   ├── sql-parse-constraint.test.ts
    │   │   ├── sql-parse-table.test.ts
    │   │   ├── sql-parse-table.ts
    │   │   ├── sql-parse-trigger.test.ts
    │   │   ├── sql-parse-trigger.ts
    │   │   ├── sql-parse-view.test.ts
    │   │   ├── sql-parse-view.ts
    │   │   ├── sqlite-dialect.ts
    │   │   ├── sqlite-generate-schema.test.ts
    │   │   └── sqlite-generate-schema.ts
    ├── env.ts
    ├── extensions
    │   ├── column-descriptor
    │   │   └── index.tsx
    │   ├── data-catalog
    │   │   ├── data-catalog-entry-modal.tsx
    │   │   ├── data-catalog-tab.tsx
    │   │   ├── data-catalog-table-accordion.tsx
    │   │   ├── data-catalog-table-column-modal.tsx
    │   │   ├── data-catalog-table-column.tsx
    │   │   ├── data-model-tab.tsx
    │   │   ├── driver-inmemory.ts
    │   │   ├── driver.tsx
    │   │   ├── empty-definition.tsx
    │   │   ├── index.tsx
    │   │   ├── sidebar.tsx
    │   │   ├── table-metadata-modal.tsx
    │   │   ├── table-result-header.tsx
    │   │   ├── term-definition-list.tsx
    │   │   ├── virtual-column.tsx
    │   │   └── virtual-join-modal.tsx
    │   ├── data-decorator
    │   │   ├── editor.tsx
    │   │   └── index.tsx
    │   ├── dolt
    │   │   ├── dolt-create-branch.tsx
    │   │   ├── dolt-sidebar.tsx
    │   │   └── index.tsx
    │   ├── local-setting-sidebar
    │   │   └── index.tsx
    │   ├── outerbase
    │   │   └── index.tsx
    │   ├── query-console-log
    │   │   └── index.ts
    │   ├── trigger-editor
    │   │   ├── index.tsx
    │   │   ├── trigger-controller.tsx
    │   │   ├── trigger-editor.tsx
    │   │   ├── trigger-save-dialog.tsx
    │   │   └── trigger-tab.tsx
    │   └── view-editor
    │   │   ├── index.tsx
    │   │   ├── view-controller.tsx
    │   │   ├── view-editor.tsx
    │   │   └── view-tab.tsx
    ├── indexdb.ts
    ├── lib
    │   ├── ai-agent-storage.ts
    │   ├── bit-operation.ts
    │   ├── build-table-result.ts
    │   ├── convertNumber.ts
    │   ├── dnd-kit.ts
    │   ├── download-file.ts
    │   ├── empty-state.ts
    │   ├── encryption.ts
    │   ├── export-helper.ts
    │   ├── generate-id.ts
    │   ├── json-safe.ts
    │   ├── key-matcher.ts
    │   ├── sql
    │   │   ├── multiple-query.ts
    │   │   ├── sql-execute-helper.ts
    │   │   └── sql-generate.schema.ts
    │   ├── tracking-throttle.ts
    │   ├── tracking.ts
    │   ├── type-utils.ts
    │   ├── utils-datetime.ts
    │   ├── utils.ts
    │   ├── v8-derialization
    │   │   ├── deserialize.test.ts
    │   │   └── index.ts
    │   ├── validation.test.ts
    │   └── validation.ts
    ├── mdx-components.tsx
    ├── outerbase-cloud
    │   ├── api-account.ts
    │   ├── api-board.ts
    │   ├── api-data-catalog.ts
    │   ├── api-type.ts
    │   ├── api-workspace.ts
    │   ├── api.ts
    │   ├── data-catalog-driver.ts
    │   ├── database-source
    │   │   └── index.tsx
    │   ├── database
    │   │   ├── query.ts
    │   │   └── utils.ts
    │   ├── hook.ts
    │   └── query-driver.ts
    └── window.d.ts
├── tsconfig.json
└── wrangler.jsonc


/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next


--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "extends": [
 3 |     "next/core-web-vitals",
 4 |     "eslint:recommended",
 5 |     "plugin:@typescript-eslint/recommended"
 6 |   ],
 7 |   "rules": {
 8 |     "@next/next/no-img-element": "off",
 9 |     "@typescript-eslint/no-explicit-any": "off",
10 |     "@typescript-eslint/ban-ts-comment": "off"
11 |   },
12 |   "parser": "@typescript-eslint/parser",
13 |   "plugins": ["@typescript-eslint"],
14 |   "overrides": [
15 |     {
16 |       "files": ["*.test.ts"],
17 |       "plugins": ["jest"],
18 |       "extends": ["plugin:jest/recommended"],
19 |       "rules": { "jest/prefer-expect-assertions": "off" }
20 |     }
21 |   ]
22 | }
23 | 


--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.tsx text eol=lf
2 | *.ts text eol=lf


--------------------------------------------------------------------------------
/.github/workflows/check.yaml:
--------------------------------------------------------------------------------
 1 | name: Check
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - develop
 7 |   pull_request:
 8 |   workflow_dispatch:
 9 | 
10 | jobs:
11 |   quality-check:
12 |     runs-on: ubuntu-latest
13 |     steps:
14 |       - name: Checkout code
15 |         uses: actions/checkout@v4
16 | 
17 |       - uses: actions/setup-node@v3
18 |         with:
19 |           node-version: 20
20 | 
21 |       - name: Install dependencies
22 |         run: npm install
23 | 
24 |       - name: Run Check
25 |         run: npm run staged
26 | 
27 |       - name: Log success
28 |         run: echo "✅ Success!"
29 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 2 | 
 3 | # dependencies
 4 | /node_modules
 5 | /.open-next
 6 | **/node_modules
 7 | /.pnp
 8 | .pnp.js
 9 | .yarn/install-state.gz
10 | 
11 | # testing
12 | /coverage
13 | 
14 | # next.js
15 | **/.next
16 | /.next/
17 | /out/
18 | 
19 | # production
20 | /build
21 | **/dist
22 | 
23 | # misc
24 | **/.DS_Store
25 | .DS_Store
26 | *.pem
27 | 
28 | # debug
29 | npm-debug.log*
30 | yarn-debug.log*
31 | yarn-error.log*
32 | 
33 | # local env files
34 | .env*.local
35 | .env
36 | 
37 | # vercel
38 | .vercel
39 | 
40 | # turbo
41 | **/.turbo
42 | .turbo
43 | 
44 | # typescript
45 | **/*.tsbuildinfo
46 | *.tsbuildinfo
47 | next-env.d.ts
48 | 
49 | certificates


--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
 1 | package-lock.json
 2 | .next
 3 | dist
 4 | drizzle/*
 5 | **/*/drizzle
 6 | .prettierignore
 7 | .gitignore
 8 | 
 9 | .changeset/
10 | 
11 | wrangler.jsonc


--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "trailingComma": "es5",
 3 |   "tabWidth": 2,
 4 |   "semi": true,
 5 |   "singleQuote": false,
 6 |   "plugins": [
 7 |     "prettier-plugin-tailwindcss"
 8 |   ]
 9 | }
10 | 


--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "editor.formatOnSave": true,
 3 |   "typescript.tsdk": "node_modules\\typescript\\lib",
 4 |   "files.eol": "\n",
 5 |   "editor.tabSize": 2,
 6 |   "editor.codeActionsOnSave": {
 7 |     "source.organizeImports": "explicit"
 8 |   },
 9 |   "jest.runMode": "on-demand"
10 | }
11 | 


--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
 1 | FROM node:20-alpine AS builder
 2 | 
 3 | # Setting working directory. All the path will be relative to WORKDIR
 4 | WORKDIR /app
 5 | # Installing dependencies
 6 | COPY package*.json ./
 7 | RUN npm install
 8 | 
 9 | # Copying source files
10 | COPY . .
11 | 
12 | # Building app
13 | RUN npm run build
14 | 
15 | # Copy only standalone server to new image
16 | FROM node:20-alpine
17 | WORKDIR /app
18 | COPY --from=builder /app/.next/standalone ./
19 | COPY --from=builder /app/public ./public
20 | COPY --from=builder /app/.next/static ./.next/static
21 | CMD ["node", "server.js"]


--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
 1 | # Security Policy
 2 | 
 3 | The Outerbase Studio team takes security seriously. If you discover a security issue, please report it immediately. Your security reports are highly valued!
 4 | 
 5 | ## Reporting a Vulnerability
 6 | 
 7 | Please DO NOT create a public issue. Instead, report the issue privately by emailing invisal@outerbase.com.
 8 | 
 9 | ## Supported Version
10 | 
11 | We exclusively support the latest release and the main branch.
12 | 


--------------------------------------------------------------------------------
/build-dialect.js:
--------------------------------------------------------------------------------
 1 | /* eslint-disable @typescript-eslint/no-var-requires */
 2 | const fs = require("fs");
 3 | const path = require("path");
 4 | const showdown = require("showdown");
 5 | 
 6 | function build_dialect(dialectName) {
 7 |   const dialectFolder = path.join(__dirname, "src", "drivers", dialectName);
 8 |   const functionFolder = path.join(dialectFolder, "functions");
 9 |   const functionFiles = fs.readdirSync(functionFolder);
10 | 
11 |   const functions = {};
12 | 
13 |   const mdConverter = new showdown.Converter({ tables: true });
14 |   for (const functionFile of functionFiles) {
15 |     const mdContent = fs
16 |       .readFileSync(path.join(functionFolder, functionFile))
17 |       .toString();
18 | 
19 |     const mdContentLines = mdContent.split("\n");
20 | 
21 |     functions[path.parse(functionFile).name] = {
22 |       syntax: mdContentLines[0],
23 |       description: mdConverter.makeHtml(mdContentLines.slice(2).join("\n")),
24 |     };
25 |   }
26 | 
27 |   fs.writeFileSync(
28 |     path.join(dialectFolder, "function-tooltip.json"),
29 |     JSON.stringify(functions, undefined, 2)
30 |   );
31 | }
32 | 
33 | build_dialect("sqlite");
34 | 


--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "$schema": "https://ui.shadcn.com/schema.json",
 3 |   "style": "new-york",
 4 |   "rsc": true,
 5 |   "tsx": true,
 6 |   "tailwind": {
 7 |     "config": "tailwind.config.js",
 8 |     "css": "src/app/globals.css",
 9 |     "baseColor": "slate",
10 |     "cssVariables": true,
11 |     "prefix": ""
12 |   },
13 |   "aliases": {
14 |     "components": "@/components",
15 |     "utils": "@/lib/utils"
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
 1 | This is working in progress about how to extend our studio functionality
 2 | 
 3 | ## Extensions
 4 | 
 5 | We have transitioned our architecture to an extension-based approach, where most features will be implemented as extensions. This shift allows new contributors to easily contribute to the codebase without requiring in-depth knowledge of the entire system.
 6 | 
 7 | This is the minimalist example of extension
 8 | 
 9 | ```typescript
10 | export default class SampleExtension extends StudioExtension {
11 |   extensionName = "sample-extension";
12 | 
13 |   init(studio: StudioExtensionContext): void {
14 |     // this is where we extend studio functionality
15 |   }
16 | }
17 | ```
18 | 
19 | - All of the extension is located at `/src/extensions`.
20 | - Once you finish implement your extension, you can attach it to studio at `/src/core/standard-extension.tsx`
21 | 
22 | Below is a list of areas where extensions can build upon our core Outerbase Studio.
23 | 
24 | - [Sidebar](sidebar.md)
25 | - [Window Tab](window-tab.md)
26 | - Resource Creation Menu
27 | - Resource Context Menu
28 | - Query Hook
29 | 


--------------------------------------------------------------------------------
/docs/sidebar.md:
--------------------------------------------------------------------------------
 1 | ## Extending Sidebar
 2 | 
 3 | To create extension that add new sidebar with content.
 4 | 
 5 | ```tsx
 6 | function SampleSidebar() {
 7 |   return <div>Sidebar Content</div>;
 8 | }
 9 | 
10 | export default class SampleExtension extends StudioExtension {
11 |   extensionName = "sample-extension";
12 | 
13 |   init(studio: StudioExtensionContext): void {
14 |     studio.registerSidebar({
15 |       key: "sample-extension-sidebar",
16 |       name: "Sample",
17 |       icon: <LucideArrow />,
18 |       content: <SampleSidebar />,
19 |     });
20 |   }
21 | }
22 | ```
23 | 
24 | You can also create sidebar without content. You need just need to provide `onClick` instead of `content`
25 | 
26 | ```tsx
27 | export default class SampleExtension extends StudioExtension {
28 |   extensionName = "sample-extension";
29 | 
30 |   init(studio: StudioExtensionContext): void {
31 |     studio.registerSidebar({
32 |       key: "sample-extension-sidebar",
33 |       name: "Sample",
34 |       icon: <LucideArrow />,
35 |       onClick: () => {
36 |         // do something
37 |       },
38 |     });
39 |   }
40 | }
41 | ```
42 | 


--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
 1 | import type { Config } from "jest";
 2 | import nextJest from "next/jest.js";
 3 | 
 4 | const createJestConfig = nextJest({
 5 |   // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
 6 |   dir: "./",
 7 | });
 8 | 
 9 | // Add any custom config to be passed to Jest
10 | const config: Config = {
11 |   coveragePathIgnorePatterns: ["<rootDir>/.*.tsx
quot;],
12 |   globals: {
13 |     window: {},
14 |   },
15 |   testEnvironment: "node",
16 |   // Add more setup options before each test is run
17 |   setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
18 | };
19 | 
20 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
21 | const overrideJestConfig = async (): Promise<Config> => {
22 |   const c: Config = await createJestConfig(config)();
23 |   c.transformIgnorePatterns = [
24 |     "/node_modules/(?!(lucia)|(oslo)|(arctic)|(@lucia-auth)|(@t3-oss))",
25 |   ];
26 |   return c;
27 | };
28 | 
29 | export default overrideJestConfig;
30 | 


--------------------------------------------------------------------------------
/jest.setup.ts:
--------------------------------------------------------------------------------
1 | import crypto from "crypto";
2 | 
3 | Object.defineProperty(window, "crypto", {
4 |   value: {
5 |     randomUUID: crypto.randomUUID,
6 |   },
7 | });
8 | 


--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
 1 | /* eslint-disable @typescript-eslint/no-var-requires */
 2 | const withMDX = require("@next/mdx")();
 3 | const pkg = require("./package.json");
 4 | 
 5 | /** @type {import('next').NextConfig} */
 6 | const nextConfig = {
 7 |   output: "standalone",
 8 |   reactStrictMode: false,
 9 |   pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
10 |   env: {
11 |     NEXT_PUBLIC_STUDIO_VERSION: pkg.version,
12 |   },
13 |   async rewrites() {
14 |     return [
15 |       {
16 |         source: "/api/v1/:path*",
17 |         destination: `${process.env.NEXT_PUBLIC_OB_API ?? "https://app.dev.outerbase.com/api/v1"}/:path*`,
18 |       },
19 |     ];
20 |   },
21 | };
22 | 
23 | module.exports = { ...withMDX(nextConfig), output: "standalone" };
24 | 


--------------------------------------------------------------------------------
/open-next.config.ts:
--------------------------------------------------------------------------------
1 | // default open-next.config.ts file created by @opennextjs/cloudflare
2 | import { defineCloudflareConfig } from "@opennextjs/cloudflare/config";
3 | import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache";
4 | 
5 | export default defineCloudflareConfig({
6 |   incrementalCache: kvIncrementalCache,
7 | });
8 | 


--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   plugins: {
3 |     '@tailwindcss/postcss': {},
4 |   },
5 | };
6 | 


--------------------------------------------------------------------------------
/public/assets/clouds.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/assets/clouds.jpg


--------------------------------------------------------------------------------
/public/assets/login-planet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/assets/login-planet.png


--------------------------------------------------------------------------------
/public/assets/login-portal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/assets/login-portal.png


--------------------------------------------------------------------------------
/public/assets/login-stars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/assets/login-stars.png


--------------------------------------------------------------------------------
/public/assets/reset-password-orb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/assets/reset-password-orb.webp


--------------------------------------------------------------------------------
/public/assets/sat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/assets/sat.png


--------------------------------------------------------------------------------
/public/caret.svg:
--------------------------------------------------------------------------------
1 | <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 | <path d="M20.281 9.28104L12.781 16.781C12.7114 16.8508 12.6287 16.9061 12.5376 16.9438C12.4466 16.9816 12.349 17.001 12.2504 17.001C12.1519 17.001 12.0543 16.9816 11.9632 16.9438C11.8722 16.9061 11.7894 16.8508 11.7198 16.781L4.21979 9.28104C4.07906 9.14031 4 8.94944 4 8.75042C4 8.55139 4.07906 8.36052 4.21979 8.21979C4.36052 8.07906 4.55139 8 4.75042 8C4.94944 8 5.14031 8.07906 5.28104 8.21979L12.2504 15.1901L19.2198 8.21979C19.2895 8.15011 19.3722 8.09483 19.4632 8.05712C19.5543 8.01941 19.6519 8 19.7504 8C19.849 8 19.9465 8.01941 20.0376 8.05712C20.1286 8.09483 20.2114 8.15011 20.281 8.21979C20.3507 8.28947 20.406 8.3722 20.4437 8.46324C20.4814 8.55429 20.5008 8.65187 20.5008 8.75042C20.5008 8.84896 20.4814 8.94654 20.4437 9.03759C20.406 9.12863 20.3507 9.21136 20.281 9.28104Z" fill="#737373"/>
3 | </svg>
4 | 


--------------------------------------------------------------------------------
/public/cloudflare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/cloudflare.png


--------------------------------------------------------------------------------
/public/data-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/data-editor.png


--------------------------------------------------------------------------------
/public/edit-table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/edit-table.png


--------------------------------------------------------------------------------
/public/extension/chart-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/extension/chart-dark.png


--------------------------------------------------------------------------------
/public/extension/chart-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/extension/chart-light.png


--------------------------------------------------------------------------------
/public/extension/definition-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/extension/definition-dark.png


--------------------------------------------------------------------------------
/public/extension/definition-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/extension/definition-light.png


--------------------------------------------------------------------------------
/public/extension/term-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/extension/term-dark.png


--------------------------------------------------------------------------------
/public/extension/term-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/extension/term-light.png


--------------------------------------------------------------------------------
/public/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/google.png


--------------------------------------------------------------------------------
/public/hero-banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/hero-banner.jpg


--------------------------------------------------------------------------------
/public/icons/outerbase.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/icons/outerbase.ico


--------------------------------------------------------------------------------
/public/icons/outerbase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/icons/outerbase.png


--------------------------------------------------------------------------------
/public/icons/outerbase.svg:
--------------------------------------------------------------------------------
1 | <svg width="32" height="33" viewBox="0 0 32 33" fill="#000" xmlns="http://www.w3.org/2000/svg">
2 | <path fill-rule="evenodd" clip-rule="evenodd" d="M16 0.400024C7.17638 0.400024 0 7.57335 0 16.3999C0 25.2182 7.17638 32.4 16 32.4C24.8235 32.4 32 25.2267 32 16.3999C32 7.57335 24.8235 0.400024 16 0.400024ZM21.9476 27.4141L21.8674 27.5173C21.0305 28.5421 20.0066 28.878 19.2942 28.9727C19.1073 28.9985 18.9203 29.0072 18.7245 29.0072C16.7835 29.0072 14.8247 27.6896 13.0617 25.1836C11.6282 23.1427 10.3817 20.4216 9.55368 17.5194C8.08456 12.3439 8.25373 7.62496 9.98105 5.48938C10.818 4.46456 11.8419 4.12871 12.5542 4.03403C14.5754 3.75832 16.641 4.93816 18.5108 7.4354C20.0599 9.50231 21.3956 12.3527 22.2861 15.4785C23.7373 20.5766 23.5948 25.244 21.9476 27.4141Z" fill="#fff"/>
3 | </svg>
4 | 


--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
 1 | <svg width="320" height="320" viewBox="0 0 320 320" fill="none" xmlns="http://www.w3.org/2000/svg">
 2 | <rect width="320" height="320" fill="url(#paint0_linear_16_2)"/>
 3 | <path d="M141.75 252V197L93.75 224.5L75.75 193L123.25 165.5L75.75 138L93.75 106.5L141.75 134V79H178.25V134L225.75 106.5L243.75 138L196.25 165.5L243.75 193L225.75 224.5L178.25 197V252H141.75Z" fill="white"/>
 4 | <defs>
 5 | <linearGradient id="paint0_linear_16_2" x1="0" y1="0" x2="320" y2="320" gradientUnits="userSpaceOnUse">
 6 | <stop stop-color="#12C2E9"/>
 7 | <stop offset="0.5" stop-color="#C471ED"/>
 8 | <stop offset="1" stop-color="#F7797D"/>
 9 | </linearGradient>
10 | </defs>
11 | </svg>
12 | 


--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
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>


--------------------------------------------------------------------------------
/public/open-source.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/open-source.png


--------------------------------------------------------------------------------
/public/outerbase-banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/outerbase-banner.jpg


--------------------------------------------------------------------------------
/public/rqlite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/rqlite.png


--------------------------------------------------------------------------------
/public/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/screenshot.png


--------------------------------------------------------------------------------
/public/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/screenshot2.png


--------------------------------------------------------------------------------
/public/social/github.svg:
--------------------------------------------------------------------------------
1 | <svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="currentColor"/></svg>


--------------------------------------------------------------------------------
/public/social/twitter.svg:
--------------------------------------------------------------------------------
1 | <svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
2 | <path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="currentColor"/>
3 | </svg>
4 | 


--------------------------------------------------------------------------------
/public/sql-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/sql-editor.png


--------------------------------------------------------------------------------
/public/sqlite-icon.svg:
--------------------------------------------------------------------------------
1 | <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 6.554 6.555" preserveAspectRatio="xMidYMid"><defs><linearGradient x1="2.983" y1=".53" x2="2.983" y2="4.744" id="A" gradientUnits="userSpaceOnUse"><stop stop-color="#97d9f6" offset="0%"/><stop stop-color="#0f80cc" offset="92.024%"/><stop stop-color="#0f80cc" offset="100%"/></linearGradient></defs><path d="M4.96.29H.847c-.276 0-.5.226-.5.5v4.536c0 .276.226.5.5.5h2.71c-.03-1.348.43-3.964 1.404-5.54z" fill="#0f80cc"/><path d="M4.81.437H.847c-.196 0-.355.16-.355.355v4.205c.898-.345 2.245-.642 3.177-.628A28.93 28.93 0 0 1 4.811.437z" fill="url(#A)"/><path d="M5.92.142c-.282-.25-.623-.15-.96.148l-.15.146c-.576.61-1.1 1.742-1.276 2.607a2.38 2.38 0 0 1 .148.426l.022.1.022.102s-.005-.02-.026-.08l-.014-.04a.461.461 0 0 0-.009-.022c-.038-.087-.14-.272-.187-.352a8.789 8.789 0 0 0-.103.321c.132.242.212.656.212.656s-.007-.027-.04-.12c-.03-.083-.176-.34-.21-.4-.06.22-.083.368-.062.404.04.07.08.2.115.324a7.52 7.52 0 0 1 .132.666l.005.062a6.11 6.11 0 0 0 .015.75c.026.313.075.582.137.726l.042-.023c-.09-.284-.128-.655-.112-1.084.025-.655.175-1.445.454-2.268C4.548 1.938 5.2.94 5.798.464c-.545.492-1.282 2.084-1.502 2.673-.247.66-.422 1.28-.528 1.873.182-.556.77-.796.77-.796s.29-.356.626-.865l-.645.172-.208.092s.53-.323.987-.47c.627-.987 1.31-2.39.622-3.002" fill="#003b57"/></svg>


--------------------------------------------------------------------------------
/public/sqljs/sql-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/sqljs/sql-wasm.wasm


--------------------------------------------------------------------------------
/public/turso.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/turso.jpeg


--------------------------------------------------------------------------------
/public/turso.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/public/turso.png


--------------------------------------------------------------------------------
/public/valtown.svg:
--------------------------------------------------------------------------------
 1 | <svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
 2 | <rect width="400" height="400" fill="black"/>
 3 | <g clip-path="url(#clip0_1045_837)">
 4 | <path d="M265.026 271.002C257.83 271.002 251.994 268.767 247.518 264.293C243.038 259.821 240.802 253.841 240.802 246.363V184.761H226.364V161.881H240.802V128H268.548V161.881H298.5V184.761H268.548V241.521C268.548 245.921 270.604 248.123 274.716 248.123H295.856V271.002H265.026Z" fill="white"/>
 5 | <path d="M204.362 174.325L158.23 250.768H154.266V178.601C154.266 169.37 146.776 161.887 137.536 161.887H126.518V253.01C126.518 262.95 134.586 271.01 144.536 271.01H163.396C173.396 271.01 182.638 265.682 187.64 257.03L242.664 161.887H226.404C217.384 161.887 209.02 166.606 204.362 174.325Z" fill="white"/>
 6 | <path d="M99.9939 161.887H127.8V184.769H99.9939V161.887Z" fill="white"/>
 7 | </g>
 8 | <defs>
 9 | <clipPath id="clip0_1045_837">
10 | <rect width="200" height="143.86" fill="white" transform="translate(100 128)"/>
11 | </clipPath>
12 | </defs>
13 | </svg>
14 | 


--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 | <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>


--------------------------------------------------------------------------------
/src/app/(dark-only)/layout.tsx:
--------------------------------------------------------------------------------
 1 | import { OuterbaseSessionProvider } from "@/app/(outerbase)/session-provider";
 2 | import ClientOnly from "@/components/client-only";
 3 | import { WorkspaceProvider } from "../(outerbase)/workspace-provider";
 4 | import ThemeLayout from "../(theme)/theme_layout";
 5 | 
 6 | export default function OuterbaseLayout({
 7 |   children,
 8 | }: {
 9 |   children: React.ReactNode;
10 | }) {
11 |   return (
12 |     <ThemeLayout overrideTheme="dark">
13 |       <ClientOnly>
14 |         <OuterbaseSessionProvider>
15 |           <WorkspaceProvider>{children}</WorkspaceProvider>
16 |         </OuterbaseSessionProvider>
17 |       </ClientOnly>
18 |     </ThemeLayout>
19 |   );
20 | }
21 | 


--------------------------------------------------------------------------------
/src/app/(dark-only)/signin/starbase-portal.tsx:
--------------------------------------------------------------------------------
 1 | import "./styles.css";
 2 | 
 3 | export function LoginBaseSpaceship() {
 4 |   return (
 5 |     <div
 6 |       className="absolute top-0 bottom-0 opacity-40 md:opacity-100"
 7 |       style={{
 8 |         left: "50%",
 9 |         height: "100vh",
10 |         width: "100vh",
11 |         transform: "translateX(-50%)",
12 |       }}
13 |     >
14 |       <div
15 |         className="absolute z-1 flex h-full w-full overflow-hidden bg-no-repeat"
16 |         style={{
17 |           backgroundImage: `url("/assets/login-portal.png")`,
18 |           backgroundSize: "contain",
19 |           backgroundPosition: "center",
20 |           backgroundColor: "transparent",
21 |         }}
22 |       ></div>
23 | 
24 |       <div
25 |         className="absolute left-1 z-0 h-full w-full"
26 |         style={{ width: "calc(100% - 8px)" }}
27 |       >
28 |         <div
29 |           className="absolute h-full w-full"
30 |           style={{ backgroundColor: "#0d1013" }}
31 |         >
32 |           <img
33 |             src="/assets/login-stars.png"
34 |             alt="stars"
35 |             className="stars-animation absolute w-full"
36 |           />
37 | 
38 |           <img
39 |             src="/assets/login-planet.png"
40 |             alt="planet"
41 |             className="planet-animation absolute w-full"
42 |             style={{ bottom: "16%" }}
43 |           />
44 |         </div>
45 |       </div>
46 |     </div>
47 |   );
48 | }
49 | 


--------------------------------------------------------------------------------
/src/app/(dark-only)/signin/styles.css:
--------------------------------------------------------------------------------
 1 | @keyframes moveStars {
 2 |   0% {
 3 |     bottom: 32%;
 4 |     transform: rotate(0deg);
 5 |   }
 6 |   25% {
 7 |     bottom: 33%;
 8 |     transform: rotate(-1deg);
 9 |   }
10 |   50% {
11 |     bottom: 34%;
12 |     transform: rotate(0deg);
13 |   }
14 |   75% {
15 |     bottom: 33%;
16 |     transform: rotate(1deg);
17 |   }
18 |   100% {
19 |     bottom: 32%;
20 |     transform: rotate(0deg);
21 |   }
22 | }
23 | 
24 | @keyframes movePlanet {
25 |   0% {
26 |     bottom: 16%;
27 |     transform: rotate(0deg);
28 |   }
29 |   25% {
30 |     bottom: 17.5%;
31 |     transform: rotate(1deg);
32 |   }
33 |   50% {
34 |     bottom: 20%;
35 |     transform: rotate(0deg);
36 |   }
37 |   75% {
38 |     bottom: 17.5%;
39 |     transform: rotate(-1deg);
40 |   }
41 |   100% {
42 |     bottom: 16%;
43 |     transform: rotate(0deg);
44 |   }
45 | }
46 | 
47 | .stars-animation {
48 |   animation: moveStars 30s ease-in-out infinite;
49 | }
50 | 
51 | .planet-animation {
52 |   animation: movePlanet 30s ease-in-out infinite;
53 | }
54 | 


--------------------------------------------------------------------------------
/src/app/(dark-only)/signup/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { useSearchParams } from "next/navigation";
 3 | import { LoginBaseSpaceship } from "../signin/starbase-portal";
 4 | import { SignupForm } from "./signup-form";
 5 | import { SignupOtp } from "./signup-otp";
 6 | 
 7 | //https://app.outerbase.com/api/v1/auth/register
 8 | export default function SignupPage() {
 9 |   const searchParams = useSearchParams();
10 | 
11 |   const verify = searchParams.get("verify") === "true";
12 | 
13 |   return (
14 |     <>
15 |       {verify ? <SignupOtp /> : <SignupForm />}
16 |       <LoginBaseSpaceship />
17 |     </>
18 |   );
19 | }
20 | 


--------------------------------------------------------------------------------
/src/app/(dark-only)/spaceship-container.tsx:
--------------------------------------------------------------------------------
 1 | import { PropsWithChildren } from "react";
 2 | 
 3 | export function SpaceshipContentContainer({ children }: PropsWithChildren) {
 4 |   return (
 5 |     <div
 6 |       className="absolute left-[10%] z-2 flex w-[400px] flex-col gap-4 rounded-lg border border-neutral-800 bg-neutral-900 p-8 md:m-0"
 7 |       style={{
 8 |         top: "50%",
 9 |         transform: "translateY(-50%)",
10 |       }}
11 |     >
12 |       {children}
13 |     </div>
14 |   );
15 | }
16 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/account/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { useSession } from "@/app/(outerbase)/session-provider";
 3 | import NavigationLayout from "../nav-layout";
 4 | import AccountFooter from "./account-footer";
 5 | import TwoFactorAuth from "./two-factor-auth";
 6 | import UserAvatar from "./user-avatar";
 7 | import UserFormInput from "./user-form-input";
 8 | 
 9 | export default function AccountPage() {
10 |   const { session } = useSession();
11 | 
12 |   if (!session?.user) return <div>You are not login!</div>;
13 | 
14 |   return (
15 |     <NavigationLayout>
16 |       <main className="flex max-w-sm flex-col p-8">
17 |         <div className="flex flex-col gap-5">
18 |           <h1 className="text-primary text-2xl font-bold">Account Setting</h1>
19 |           <h2 className="mt-5 text-xl font-medium text-balance text-neutral-800 dark:text-neutral-100">
20 |             Details
21 |           </h2>
22 |           <UserAvatar user={session.user} />
23 |           <UserFormInput user={session.user} />
24 |           <TwoFactorAuth />
25 |           <AccountFooter />
26 |         </div>
27 |       </main>
28 |     </NavigationLayout>
29 |   );
30 | }
31 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/account/user-avatar.tsx:
--------------------------------------------------------------------------------
 1 | import { Button } from "@/components/orbit/button";
 2 | import { OuterbaseAPIUser } from "@/outerbase-cloud/api-type";
 3 | import Image from "next/image";
 4 | 
 5 | export default function UserAvatar({ user }: { user: OuterbaseAPIUser }) {
 6 |   // not implement upload user avatar
 7 | 
 8 |   return (
 9 |     <div className="flex items-center gap-5">
10 |       <div className="flex size-24 items-center justify-center rounded-full bg-neutral-200 text-center dark:bg-neutral-800">
11 |         {user.avatar ? (
12 |           <Image
13 |             src={user.avatar}
14 |             alt="User Avatar"
15 |             fill
16 |             className="rounded-full object-cover"
17 |           />
18 |         ) : (
19 |           <span className="text-4xl">{user.initials}</span>
20 |         )}
21 |       </div>
22 |       <Button title="Add Avatar" size="lg" />
23 |     </div>
24 |   );
25 | }
26 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/auth-provider.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { usePathname, useRouter } from "next/navigation";
 3 | import { PropsWithChildren, useEffect } from "react";
 4 | import { useSession } from "./session-provider";
 5 | 
 6 | export default function AuthProvider({ children }: PropsWithChildren) {
 7 |   const router = useRouter();
 8 |   const pathname = usePathname();
 9 |   const { isLoading, session } = useSession();
10 | 
11 |   useEffect(() => {
12 |     if (isLoading) return;
13 |     const redirect = localStorage.getItem("continue-redirect");
14 |     if (!session?.session || !session?.user) {
15 |       localStorage.setItem("continue-redirect", pathname);
16 |       router.replace("/signin");
17 |       // IF user enabled 2FA keep redirect to verify page
18 |     } else if (session.user.has_otp && redirect === "/verify") {
19 |       router.replace(redirect);
20 |     } else {
21 |       localStorage.removeItem("continue-redirect");
22 |     }
23 |   }, [isLoading, session, pathname, router]);
24 | 
25 |   return <>{children}</>;
26 | }
27 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/base-template.tsx:
--------------------------------------------------------------------------------
 1 | import {
 2 |   CommonConnectionConfig,
 3 |   CommonConnectionConfigTemplate,
 4 | } from "@/components/connection-config-editor";
 5 | import { OuterbaseAPISourceInput } from "@/outerbase-cloud/api-type";
 6 | import { ReactElement } from "react";
 7 | import { SavedConnectionRawLocalStorage } from "../(theme)/connect/saved-connection-storage";
 8 | 
 9 | export interface ConnectionTemplateList {
10 |   template: CommonConnectionConfigTemplate;
11 |   localFrom?: (value: SavedConnectionRawLocalStorage) => CommonConnectionConfig;
12 |   localTo?: (value: CommonConnectionConfig) => SavedConnectionRawLocalStorage;
13 | 
14 |   /**
15 |    * Convert the remote source config to common connecting config
16 |    * @param value
17 |    * @returns
18 |    */
19 |   remoteFrom?: (value: {
20 |     source: OuterbaseAPISourceInput;
21 |     name: string;
22 |   }) => CommonConnectionConfig;
23 | 
24 |   /**
25 |    * Convert the common connecting config to remote source config
26 |    * @param value
27 |    * @returns
28 |    */
29 |   remoteTo?: (value: CommonConnectionConfig) => {
30 |     source: OuterbaseAPISourceInput;
31 |     name: string;
32 |   };
33 |   instruction?: ReactElement;
34 | }
35 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/layout.tsx:
--------------------------------------------------------------------------------
 1 | import { OuterbaseSessionProvider } from "@/app/(outerbase)/session-provider";
 2 | import ClientOnly from "@/components/client-only";
 3 | import ThemeLayout from "../(theme)/theme_layout";
 4 | import { WorkspaceProvider } from "./workspace-provider";
 5 | 
 6 | export default function OuterbaseLayout({
 7 |   children,
 8 | }: {
 9 |   children: React.ReactNode;
10 | }) {
11 |   return (
12 |     <ThemeLayout>
13 |       <ClientOnly>
14 |         <OuterbaseSessionProvider>
15 |           <WorkspaceProvider>{children}</WorkspaceProvider>
16 |         </OuterbaseSessionProvider>
17 |       </ClientOnly>
18 |     </ThemeLayout>
19 |   );
20 | }
21 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/local/new-base/mysql/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { CloudDriverSupportOnly } from "../cloud-support-only";
3 | 
4 | export default function LocalMySQLNewBasePage() {
5 |   return <CloudDriverSupportOnly type="mysql" />;
6 | }
7 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/local/new-base/postgres/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { CloudDriverSupportOnly } from "../cloud-support-only";
3 | 
4 | export default function LocalMySQLNewBasePage() {
5 |   return <CloudDriverSupportOnly type="postgres" />;
6 | }
7 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/nav-header-local.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/(outerbase)/nav-header-local.tsx


--------------------------------------------------------------------------------
/src/app/(outerbase)/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { useRouter } from "next/navigation";
 4 | import { useEffect } from "react";
 5 | import NavigationLayout from "./nav-layout";
 6 | import { ResourceItemList } from "./resource-item-helper";
 7 | import { useSession } from "./session-provider";
 8 | import { useWorkspaces } from "./workspace-provider";
 9 | 
10 | export default function OuterbaseMainPage() {
11 |   const router = useRouter();
12 |   const { isLoading: sessionLoading, session } = useSession();
13 |   const { workspaces, loading: workspaceLoading } = useWorkspaces();
14 | 
15 |   useEffect(() => {
16 |     if (sessionLoading) return;
17 | 
18 |     // Invalid session, go to local connection
19 |     if (!session) {
20 |       router.push("/local");
21 |     }
22 | 
23 |     if (workspaceLoading) return;
24 |     if (!workspaces) return;
25 | 
26 |     // Redirect to the first workspace
27 |     if (workspaces.length > 0) {
28 |       router.push(`/w/${workspaces[0].short_name}`);
29 |     } else {
30 |       router.push("/local");
31 |     }
32 |   }, [session, sessionLoading, workspaceLoading, workspaces, router]);
33 | 
34 |   return (
35 |     <NavigationLayout>
36 |       <div className="flex flex-1 flex-col content-start gap-4 overflow-x-hidden overflow-y-auto p-4">
37 |         <ResourceItemList boards={[]} bases={[]} loading />
38 |       </div>
39 |     </NavigationLayout>
40 |   );
41 | }
42 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/resource-card-loading.tsx:
--------------------------------------------------------------------------------
 1 | export default function ResourceCardLoading() {
 2 |   return (
 3 |     <div className="bg-background flex h-36 w-full gap-2 rounded border p-4">
 4 |       <div>
 5 |         <div className="bg bg-muted h-10 w-10 animate-pulse rounded"></div>
 6 |       </div>
 7 |       <div className="mt-1 flex flex-col gap-2">
 8 |         <div className="bg bg-muted h-3 w-24 animate-pulse rounded-sm"></div>
 9 |         <div className="bg bg-muted h-3 w-32 animate-pulse rounded-sm"></div>
10 |       </div>
11 |     </div>
12 |   );
13 | }
14 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/signout/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { Loader } from "@/components/orbit/loader";
 4 | import { useRouter } from "next/navigation";
 5 | import { useEffect } from "react";
 6 | import { useSession } from "../session-provider";
 7 | 
 8 | export default function SignoutPage() {
 9 |   const { logout } = useSession();
10 |   const router = useRouter();
11 | 
12 |   useEffect(() => {
13 |     if (typeof window === "undefined") return;
14 | 
15 |     logout();
16 |     router.push("/");
17 |   }, [router, logout]);
18 | 
19 |   return (
20 |     <div className="flex h-screen w-screen flex-col items-center justify-center gap-4">
21 |       <Loader size={40} />
22 |       <p>Signing out....</p>
23 |     </div>
24 |   );
25 | }
26 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/billing/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import NavigationHeader from "@/app/(outerbase)/nav-header";
 3 | import NavigationLayout from "@/app/(outerbase)/nav-layout";
 4 | 
 5 | export const runtime = "edge";
 6 | 
 7 | export default function WorkspaceBillingPage() {
 8 |   return (
 9 |     <NavigationLayout>
10 |       <NavigationHeader />
11 |       <div className="container mt-10 flex flex-col p-4">
12 |         <h1 className="text-lg font-bold">Billing</h1>
13 |       </div>
14 |     </NavigationLayout>
15 |   );
16 | }
17 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/edit-base/page.tsx:
--------------------------------------------------------------------------------
1 | export const runtime = "edge";
2 | 
3 | export default function NewBasePage() {
4 |   return <div>Unknown page</div>;
5 | }
6 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/layout.tsx:
--------------------------------------------------------------------------------
 1 | import { DialogProvider } from "@/components/create-dialog";
 2 | import AuthProvider from "../../auth-provider";
 3 | 
 4 | export default function OuterbaseLayout({
 5 |   children,
 6 | }: {
 7 |   children: React.ReactNode;
 8 | }) {
 9 |   return (
10 |     <AuthProvider>
11 |       {children}
12 |       <DialogProvider slot="workspace" />
13 |     </AuthProvider>
14 |   );
15 | }
16 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/new-base/page.tsx:
--------------------------------------------------------------------------------
1 | export const runtime = "edge";
2 | 
3 | export default function NewBasePage() {
4 |   return <div>Unknown page</div>;
5 | }
6 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/redirect-valid-workspace.ts:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { useParams, useRouter } from "next/navigation";
 3 | import { useEffect } from "react";
 4 | import { useWorkspaces } from "../../workspace-provider";
 5 | 
 6 | export default function useRedirectValidWorkspace() {
 7 |   const { workspaceId } = useParams();
 8 |   const { currentWorkspace, workspaces, loading } = useWorkspaces();
 9 |   const router = useRouter();
10 | 
11 |   useEffect(() => {
12 |     // If the current workspace is not found, redirect to the first workspace
13 |     if (loading) return;
14 |     if (!workspaceId) return;
15 |     if (workspaceId === "local-workspace") return;
16 |     if (typeof window === "undefined") return;
17 | 
18 |     if (!currentWorkspace) {
19 |       if (workspaces?.length) {
20 |         router.replace(`/w/${workspaces[0].short_name}`);
21 |       }
22 |     }
23 |   }, [loading, workspaces, currentWorkspace, workspaceId, router]);
24 | }
25 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/settings/gateway.tsx:
--------------------------------------------------------------------------------
 1 | import { Button } from "@/components/orbit/button";
 2 | 
 3 | export default function WorkspaceGatewaySection() {
 4 |   return (
 5 |     <div className="mt-12 flex flex-col gap-4">
 6 |       <h2 className="font-bold">Gateway</h2>
 7 | 
 8 |       <p className="text-base">
 9 |         Securely access private network databases without modifying your
10 |         firewall or VPN
11 |       </p>
12 | 
13 |       <div>
14 |         <Button size="lg" variant="secondary">
15 |           New Gateway
16 |         </Button>
17 |       </div>
18 |     </div>
19 |   );
20 | }
21 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/settings/members.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { Button } from "@/components/orbit/button";
 3 | import { Plus } from "@phosphor-icons/react";
 4 | 
 5 | export default function WorkspaceMemberSection() {
 6 |   return (
 7 |     <div className="mt-12 flex flex-col gap-4">
 8 |       <div className="flex items-center justify-between">
 9 |         <h2 className="font-bold">Members</h2>
10 |         <Button size="lg">
11 |           <Plus size={16} />
12 |           New Member
13 |         </Button>
14 |       </div>
15 |     </div>
16 |   );
17 | }
18 | 


--------------------------------------------------------------------------------
/src/app/(outerbase)/w/[workspaceId]/settings/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import NavigationHeader from "@/app/(outerbase)/nav-header";
 3 | import NavigationLayout from "@/app/(outerbase)/nav-layout";
 4 | import { useWorkspaces } from "@/app/(outerbase)/workspace-provider";
 5 | import { Loader } from "@/components/orbit/loader";
 6 | import WorkspaceDeleteSection from "./delete";
 7 | import WorkspaceDetailSection from "./detail";
 8 | 
 9 | export const runtime = "edge";
10 | 
11 | export default function WorkspaceBillingPage() {
12 |   const { currentWorkspace } = useWorkspaces();
13 | 
14 |   return (
15 |     <NavigationLayout>
16 |       <NavigationHeader />
17 | 
18 |       <div className="container mt-10 flex flex-col p-4">
19 |         <h1 className="text-lg font-bold">Workspace settings</h1>
20 | 
21 |         {currentWorkspace ? (
22 |           <>
23 |             <WorkspaceDetailSection workspace={currentWorkspace} />
24 |             {/* <WorkspaceMemberSection />
25 |             <WorkspaceGatewaySection /> */}
26 |             <WorkspaceDeleteSection workspace={currentWorkspace} />
27 |           </>
28 |         ) : (
29 |           <div className="my-12 flex flex-col items-center justify-center gap-4">
30 |             <Loader size={50} />
31 |             Loading workspace...
32 |           </div>
33 |         )}
34 |       </div>
35 |     </NavigationLayout>
36 |   );
37 | }
38 | 


--------------------------------------------------------------------------------
/src/app/(public)/about/page.tsx:
--------------------------------------------------------------------------------
1 | export default function BlogPage() {
2 |   return <div>Coming Soon</div>;
3 | }
4 | 


--------------------------------------------------------------------------------
/src/app/(public)/docs/connect-turso/page.mdx:
--------------------------------------------------------------------------------
 1 | import { DocContent } from "@/components/mdx/docs";
 2 | 
 3 | export const metadata = {
 4 |   title: "Connect to Turso using LibSQL Studio - The Best GUI Client for Turso",
 5 | };
 6 | 
 7 | <DocContent title="Connect to Turso" group="Connecting">
 8 | 
 9 | LibSQL Studio is an excellent GUI for working with the Turso database. Here is
10 | a step-by-step guide on how to connect your Turso database using LibSQL
11 | Studio.
12 | 
13 | ## Generate Token
14 | 
15 | To connect to the Turso database, you need to generate a database token. Follow these steps via the Turso dashboard:
16 | 
17 | 1. Go to the Turso Database section.
18 | 2. Navigate to the Database menu.
19 | 3. Click on **"Get Token"** on the right side of the database you want to connect.
20 | 4. Click **"Generate Token"** and then copy the token.
21 | 
22 | ## Connect
23 | 
24 | Open LibSQL Studio
25 | 
26 | 1. Click **"New Connection"** and choose **"Turso"**.
27 | 2. Enter your database URL and the token you generated earlier.
28 | 3. Click **"Connect."**
29 | 
30 | </DocContent>
31 | 


--------------------------------------------------------------------------------
/src/app/(public)/docs/connect-valtown/page.mdx:
--------------------------------------------------------------------------------
 1 | import { DocContent } from "@/components/mdx/docs";
 2 | 
 3 | export const metadata = {
 4 |   title: "Connect to Val.town Database using LibSQL Studio",
 5 | };
 6 | 
 7 | <DocContent title="Connect to Valtown" group="Connecting">
 8 | 
 9 | 
10 | LibSQL Studio is an excellent SQLite GUI client that offers extensive functionality.
11 | Since Val.town's database is based on SQLite, LibSQL Studio is the perfect tool for managing it.
12 | With LibSQL Studio, connecting to your Val.town personal database is easy. Please follow the steps below:
13 | 
14 | ## Generate Token
15 | 
16 | 1. Goto [API Token Setting](https://www.val.town/settings/api)
17 | 2. Click "New" if you haven't generated token before
18 | 3. Copy the token
19 | 
20 | ## Connect
21 | 
22 | Open LibSQL Studio
23 | 
24 | 1. Click **"New Connection"** and choose **"Valtown"**.
25 | 2. Enter your Val.town token.
26 | 3. Click **"Connect."**
27 | 
28 | </DocContent>
29 | 


--------------------------------------------------------------------------------
/src/app/(public)/docs/layout.tsx:
--------------------------------------------------------------------------------
 1 | import { DocLayout } from "@/components/mdx/docs";
 2 | import { WEBSITE_NAME } from "@/const";
 3 | 
 4 | const TableContent = [
 5 |   {
 6 |     title: WEBSITE_NAME,
 7 |     href: "/docs",
 8 |   },
 9 |   {
10 |     title: "Connecting",
11 |     sub: [
12 |       {
13 |         title: "Connect to Turso",
14 |         href: "/docs/connect-turso",
15 |       },
16 |       {
17 |         title: "Connect to Valtown",
18 |         href: "/docs/connect-valtown",
19 |       },
20 |     ],
21 |   },
22 |   {
23 |     title: "Integration",
24 |     sub: [
25 |       {
26 |         title: "Embed Client",
27 |         href: "/docs/embed-iframe-client",
28 |       },
29 |     ],
30 |   },
31 | ];
32 | 
33 | export default function MdxLayout({ children }: { children: React.ReactNode }) {
34 |   return (
35 |     <DocLayout content={TableContent} title="Document">
36 |       {children}
37 |     </DocLayout>
38 |   );
39 | }
40 | 


--------------------------------------------------------------------------------
/src/app/(public)/docs/page.mdx:
--------------------------------------------------------------------------------
 1 | import { DocContent } from "@/components/mdx/docs";
 2 | 
 3 | export const metadata = {
 4 |   title: "What's LibSQL Studio",
 5 | };
 6 | 
 7 | <DocContent title="LibSQL Studio" group="About Us">
 8 | 
 9 | LibSQL Studio is an extremely powerful and lightweight SQLite GUI that runs in your browser. It comes packed with a ton of features, including:
10 | 
11 | - A powerful data editor capable of handling thousands of rows and columns without overwhelming your RAM.
12 | - A SQL query editor with syntax highlighting, tooltips, and auto-completion to boost your productivity.
13 | - Advanced tools for editing your table schema and indexes.
14 | 
15 | </DocContent>
16 | 


--------------------------------------------------------------------------------
/src/app/(public)/layout.tsx:
--------------------------------------------------------------------------------
 1 | import { Fragment } from "react";
 2 | import ThemeLayout from "../(theme)/theme_layout";
 3 | 
 4 | export default async function RootLayout({
 5 |   children,
 6 | }: {
 7 |   children: React.ReactNode;
 8 | }) {
 9 |   return (
10 |     <ThemeLayout overrideTheme="dark">
11 |       <Fragment>{children}</Fragment>
12 |     </ThemeLayout>
13 |   );
14 | }
15 | 


--------------------------------------------------------------------------------
/src/app/(theme)/client/layout.tsx:
--------------------------------------------------------------------------------
 1 | import ThemeLayout from "../theme_layout";
 2 | 
 3 | export default async function RootLayout({
 4 |   children,
 5 | }: {
 6 |   children: React.ReactNode;
 7 | }) {
 8 |   return <ThemeLayout>{children}</ThemeLayout>;
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/app/(theme)/client/s/[[...driver]]/page.tsx:
--------------------------------------------------------------------------------
 1 | import ClientOnly from "@/components/client-only";
 2 | import ClientPageBody from "./page-client";
 3 | 
 4 | export const runtime = "edge";
 5 | 
 6 | export default function SessionPage() {
 7 |   return (
 8 |     <ClientOnly>
 9 |       <ClientPageBody />
10 |     </ClientOnly>
11 |   );
12 | }
13 | 


--------------------------------------------------------------------------------
/src/app/(theme)/embed/[driver]/page.tsx:
--------------------------------------------------------------------------------
 1 | import ClientOnly from "@/components/client-only";
 2 | import ThemeLayout from "../../theme_layout";
 3 | import EmbedPageClient from "./page-client";
 4 | 
 5 | export interface EmbedPageProps {
 6 |   searchParams: Promise<{
 7 |     theme?: string;
 8 |     disableThemeToggle?: string;
 9 |     [key: string]: any;
10 |   }>;
11 |   params: Promise<{
12 |     driver: string;
13 |   }>;
14 | }
15 | 
16 | export const runtime = "edge";
17 | 
18 | export default async function EmbedPage(props: EmbedPageProps) {
19 |   const searchParams = await props.searchParams;
20 |   const driver = (await props.params).driver;
21 | 
22 |   let overrideTheme: "dark" | "light" | undefined = undefined;
23 | 
24 |   if (searchParams.theme) {
25 |     overrideTheme = searchParams.theme === "dark" ? "dark" : "light";
26 |   }
27 | 
28 |   const overrideThemeVariables: Record<string, string> = {};
29 | 
30 |   for (const key in searchParams) {
31 |     if (!key.startsWith("themeVariables[")) {
32 |       continue;
33 |     }
34 | 
35 |     overrideThemeVariables[key.slice(15, -1)] = searchParams[key];
36 |   }
37 | 
38 |   return (
39 |     <ThemeLayout
40 |       overrideTheme={overrideTheme}
41 |       overrideThemeVariables={overrideThemeVariables}
42 |     >
43 |       <ClientOnly>
44 |         <EmbedPageClient driverName={driver} />
45 |       </ClientOnly>
46 |     </ThemeLayout>
47 |   );
48 | }
49 | 


--------------------------------------------------------------------------------
/src/app/(theme)/embed/board/[boardId]/page.tsx:
--------------------------------------------------------------------------------
 1 | import ThemeLayout from "@/app/(theme)/theme_layout";
 2 | import Chart from "@/components/chart";
 3 | import ClientOnly from "@/components/client-only";
 4 | import { getOuterbaseEmbedChart } from "@/outerbase-cloud/api";
 5 | 
 6 | interface EmbedBoardPageProps {
 7 |   params: Promise<{ boardId: string }>;
 8 |   searchParams: Promise<{ key: string; theme: string }>;
 9 | }
10 | 
11 | export const runtime = "edge";
12 | 
13 | export default async function EmbedBoardPage(props: EmbedBoardPageProps) {
14 |   const searchParams = await props.searchParams;
15 |   const params = await props.params;
16 | 
17 |   const result = await getOuterbaseEmbedChart(params.boardId, searchParams.key);
18 |   const data = result.response.result?.items ?? [];
19 | 
20 |   return (
21 |     <ThemeLayout
22 |       overrideTheme={searchParams.theme === "dark" ? "dark" : "light"}
23 |     >
24 |       <ClientOnly>
25 |         <Chart
26 |           data={data}
27 |           value={result.response as any}
28 |           className="h-screen w-screen"
29 |         />
30 |       </ClientOnly>
31 |     </ThemeLayout>
32 |   );
33 | }
34 | 


--------------------------------------------------------------------------------
/src/app/(theme)/playground/mysql/[roomName]/page.tsx:
--------------------------------------------------------------------------------
 1 | import ClientOnly from "@/components/client-only";
 2 | import { Metadata } from "next";
 3 | import ThemeLayout from "../../../theme_layout";
 4 | import MySQLPlaygroundPageClient from "./page-client";
 5 | 
 6 | export const metadata: Metadata = {
 7 |   title:
 8 |     "SQLite Online Playground - Powerful and lightweight editor on your browser",
 9 |   description:
10 |     "Explore the powerful SQLite Playground in your browser – no downloads or registration needed. Effortlessly load your SQLite files or start with a blank database, then save your work with ease. Enjoy a robust data editor, advanced query capabilities, table creation, and much more.",
11 |   keywords: [
12 |     "sqlite",
13 |     "libsql",
14 |     "browser",
15 |     "client",
16 |     "gui",
17 |     "playground",
18 |     "sandbox",
19 |     "explorer",
20 |     "studio",
21 |   ],
22 |   robots: {
23 |     index: true,
24 |     follow: true,
25 |   },
26 | };
27 | 
28 | export const runtime = "edge";
29 | 
30 | interface MySQLPlaygroundProps {
31 |   params: Promise<{ roomName: string }>;
32 | }
33 | 
34 | export default async function MySQLPlaygroundEditor(
35 |   props: MySQLPlaygroundProps
36 | ) {
37 |   const { roomName } = await props.params;
38 | 
39 |   return (
40 |     <ThemeLayout>
41 |       <ClientOnly>
42 |         <MySQLPlaygroundPageClient roomName={roomName} />
43 |       </ClientOnly>
44 |     </ThemeLayout>
45 |   );
46 | }
47 | 


--------------------------------------------------------------------------------
/src/app/(theme)/theme_layout.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import PageTracker from "@/components/page-tracker";
 3 | import { Toaster } from "@/components/ui/sonner";
 4 | import { TooltipProvider } from "@radix-ui/react-tooltip";
 5 | import { ThemeProvider } from "next-themes";
 6 | import Script from "next/script";
 7 | import { Fragment, PropsWithChildren, useEffect } from "react";
 8 | 
 9 | export default function ThemeLayout({
10 |   children,
11 |   overrideTheme,
12 |   overrideThemeVariables,
13 | }: PropsWithChildren<{
14 |   overrideTheme?: "dark" | "light";
15 |   overrideThemeVariables?: Record<string, string>;
16 | }>) {
17 |   useEffect(() => {
18 |     if (overrideThemeVariables && typeof window === "undefined") {
19 |       Object.entries(overrideThemeVariables).forEach(([key, value]) => {
20 |         document.body.style.setProperty(key, value);
21 |       });
22 |     }
23 |   }, [overrideThemeVariables]);
24 | 
25 |   return (
26 |     <>
27 |       <ThemeProvider
28 |         forcedTheme={overrideTheme}
29 |         enableColorScheme
30 |         attribute="class"
31 |       >
32 |         <TooltipProvider>
33 |           <Fragment>{children}</Fragment>
34 |         </TooltipProvider>
35 |         <Toaster />
36 |       </ThemeProvider>
37 |       <PageTracker />
38 |       <Script async defer src="https://buttons.github.io/buttons.js" />
39 |     </>
40 |   );
41 | }
42 | 


--------------------------------------------------------------------------------
/src/app/api/events/insert-tracking-record.ts:
--------------------------------------------------------------------------------
 1 | "use server";
 2 | 
 3 | import { StarbaseQuery } from "@/drivers/database/starbasedb";
 4 | import { env } from "@/env";
 5 | import { generateId } from "@/lib/generate-id";
 6 | import { escapeSqlValue } from "@outerbase/sdk-transform";
 7 | import { type TrackEventItem } from "../../../lib/tracking";
 8 | 
 9 | export async function insertTrackingRecord(
10 |   deviceId: string,
11 |   events: TrackEventItem[]
12 | ) {
13 |   if (!env.DATABASE_ANALYTIC_URL || !env.DATABASE_ANALYTIC_AUTH_TOKEN) {
14 |     return {
15 |       success: false,
16 |       error: "Analytics database is not configured",
17 |     };
18 |   }
19 | 
20 |   const trackingDb = new StarbaseQuery(
21 |     env.DATABASE_ANALYTIC_URL,
22 |     env.DATABASE_ANALYTIC_AUTH_TOKEN
23 |   );
24 | 
25 |   const sql = [
26 |     "INSERT INTO events(id, created_at, user_id, event_name, event_data) VALUES",
27 |     events
28 |       .map(
29 |         (event) =>
30 |           "(" +
31 |           [
32 |             generateId(),
33 |             Date.now(),
34 |             deviceId,
35 |             event.name,
36 |             JSON.stringify(event.data),
37 |           ]
38 |             .map(escapeSqlValue)
39 |             .join(", ") +
40 |           ")"
41 |       )
42 |       .join(", "),
43 |   ].join("");
44 | 
45 |   await trackingDb.query(sql);
46 | }
47 | 


--------------------------------------------------------------------------------
/src/app/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/apple-icon.png


--------------------------------------------------------------------------------
/src/app/connect/route.ts:
--------------------------------------------------------------------------------
 1 | // This route is maintained to redirect the old path to the new /local route.
 2 | // It ensures compatibility for users accessing the old website links
 3 | // that have not yet been updated to the new URL.
 4 | 
 5 | import { redirect } from "next/navigation";
 6 | 
 7 | export const runtime = "edge";
 8 | 
 9 | export const GET = function () {
10 |   return redirect("/local");
11 | }


--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/favicon.ico


--------------------------------------------------------------------------------
/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2


--------------------------------------------------------------------------------
/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2


--------------------------------------------------------------------------------
/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2


--------------------------------------------------------------------------------
/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2


--------------------------------------------------------------------------------
/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2


--------------------------------------------------------------------------------
/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2


--------------------------------------------------------------------------------
/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2


--------------------------------------------------------------------------------
/src/app/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outerbase/studio/8d49b2e15d3a113ac857c8662e4b6fe6f0b43045/src/app/icon.png


--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
 1 | import { WEBSITE_NAME } from "@/const";
 2 | import type { Metadata } from "next";
 3 | 
 4 | import "./codemirror-override.css";
 5 | import "./globals.css";
 6 | 
 7 | const siteDescription = `${WEBSITE_NAME} is a fully-featured, lightweight GUI client for managing SQLite-based databases like Turso, LibSQL, and rqlite. It runs entirely in your browser, so there's no need to download anything`;
 8 | 
 9 | import { DialogProvider } from "@/components/create-dialog";
10 | 
11 | export const metadata: Metadata = {
12 |   title: WEBSITE_NAME,
13 |   keywords: [
14 |     "libsql",
15 |     "rqlite",
16 |     "sqlite",
17 |     "studio",
18 |     "browser",
19 |     "editor",
20 |     "gui",
21 |     "online",
22 |     "client",
23 |   ],
24 |   description: siteDescription,
25 |   openGraph: {
26 |     siteName: WEBSITE_NAME,
27 |     description: siteDescription,
28 |   },
29 | };
30 | 
31 | export default async function RootLayout({
32 |   children,
33 | }: {
34 |   children: React.ReactNode;
35 | }) {
36 |   return (
37 |     <html lang="en" suppressHydrationWarning>
38 |       <body>
39 |         {children}
40 |         <DialogProvider slot="default" />
41 |       </body>
42 |     </html>
43 |   );
44 | }
45 | 


--------------------------------------------------------------------------------
/src/app/storybook/avatar/page.tsx:
--------------------------------------------------------------------------------
 1 | import { Avatar } from "@/components/orbit/avatar";
 2 | import Block from "@/components/orbit/block";
 3 | import Inset from "@/components/orbit/inset";
 4 | import Section from "@/components/orbit/section";
 5 | 
 6 | const userL = "logan";
 7 | const userLImg = "/logo.svg";
 8 | 
 9 | const userB = "brandon";
10 | 
11 | export default function AvatarStorybook() {
12 |   return (
13 |     <Section>
14 |       <Inset>
15 |         <Block title="Avatar">
16 |           <Avatar username={userL} image={userLImg} size="base" />
17 |           <Avatar username={userB} image={undefined} />
18 |         </Block>
19 |       </Inset>
20 |     </Section>
21 |   );
22 | }
23 | 


--------------------------------------------------------------------------------
/src/app/storybook/blank_page.mdx:
--------------------------------------------------------------------------------
1 | import { DocContent } from "@/components/mdx/docs";
2 | 
3 | export const metadata = {
4 |   title: "Template Title",
5 | };
6 | 
7 | <DocContent title="Title" group="Group"></DocContent>
8 | 


--------------------------------------------------------------------------------
/src/app/storybook/button/refresh-button.tsx:
--------------------------------------------------------------------------------
 1 | import { Button, ButtonProps } from "@/components/orbit/button";
 2 | import { cn } from "@/lib/utils";
 3 | import { ArrowsClockwise } from "@phosphor-icons/react";
 4 | 
 5 | export const RefreshButton = ({ ...props }: ButtonProps) => (
 6 |   <Button shape="square" toggled={props.toggled} {...props}>
 7 |     <ArrowsClockwise
 8 |       className={cn({
 9 |         "animate-refresh": props.toggled,
10 |         "size-4.5": props.size === "base",
11 |         "size-4": props.size === "sm",
12 |         "size-5": props.size === "lg",
13 |       })}
14 |     />
15 |   </Button>
16 | );
17 | 


--------------------------------------------------------------------------------
/src/app/storybook/column-type/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import ColumnTypeSelector from "@/components/gui/schema-editor/column-type-selector";
 3 | import { MYSQL_DATA_TYPE_SUGGESTION } from "@/drivers/mysql/mysql-data-type";
 4 | import { useState } from "react";
 5 | 
 6 | export default function ColumnTypeStorybook() {
 7 |   const [value, setValue] = useState("");
 8 | 
 9 |   return (
10 |     <div className="p-4">
11 |       <ColumnTypeSelector
12 |         value={value}
13 |         onChange={setValue}
14 |         suggestions={MYSQL_DATA_TYPE_SUGGESTION.typeSuggestions!}
15 |       />
16 |     </div>
17 |   );
18 | }
19 | 


--------------------------------------------------------------------------------
/src/app/storybook/editor-prompt-widget/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { CodeMirrorPromptWidget } from "@/components/editor/prompt-widget";
3 | 
4 | export default function StorybookEditorPromptWidget() {
5 |   return <CodeMirrorPromptWidget />;
6 | }
7 | 


--------------------------------------------------------------------------------
/src/app/storybook/label/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import Block from "@/components/orbit/block";
 4 | import { Input } from "@/components/orbit/input";
 5 | import Inset from "@/components/orbit/inset";
 6 | import { Label } from "@/components/orbit/label";
 7 | import Section from "@/components/orbit/section";
 8 | 
 9 | export default function LabelStorybook() {
10 |   return (
11 |     <Section>
12 |       <Inset>
13 |         <Block title="Label">
14 |           <Label title="Name" htmlFor="name" className="w-1/2">
15 |             <Input
16 |               onValueChange={() => {}}
17 |               placeholder="e.g. Joe Smith"
18 |               id="name"
19 |             />
20 |           </Label>
21 |         </Block>
22 |       </Inset>
23 |     </Section>
24 |   );
25 | }
26 | 


--------------------------------------------------------------------------------
/src/app/storybook/listview/page.mdx:
--------------------------------------------------------------------------------
 1 | import { StorybookListviewExample } from "@/app/storybook/listview/example1";
 2 | import { DocContent } from "@/components/mdx/docs";
 3 | 
 4 | export const metadata = {
 5 |   title: "Listview",
 6 | };
 7 | 
 8 | <DocContent title="Listview" group="Standard Components"></DocContent>
 9 | 
10 | <StorybookListviewExample />
11 | 
12 | <DocContent>You can write more markdown here</DocContent>
13 | 


--------------------------------------------------------------------------------
/src/app/storybook/loader/page.tsx:
--------------------------------------------------------------------------------
 1 | import Block from "@/components/orbit/block";
 2 | import Inset from "@/components/orbit/inset";
 3 | import { Loader } from "@/components/orbit/loader";
 4 | import Section from "@/components/orbit/section";
 5 | 
 6 | export default function LoaderStorybook() {
 7 |   return (
 8 |     <Section>
 9 |       <Inset>
10 |         <Block title="Loader">
11 |           <Loader size={40} />
12 |         </Block>
13 |       </Inset>
14 |     </Section>
15 |   );
16 | }
17 | 


--------------------------------------------------------------------------------
/src/app/storybook/menu-bar/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import Block from "@/components/orbit/block";
 4 | import Inset from "@/components/orbit/inset";
 5 | import { MenuBar, MenuBarItemProps } from "@/components/orbit/menu-bar";
 6 | import Section from "@/components/orbit/section";
 7 | import { useState } from "react";
 8 | 
 9 | const items: MenuBarItemProps[] = [
10 |   {
11 |     content: "All",
12 |     value: "all",
13 |   },
14 |   {
15 |     content: "Bases",
16 |     value: "base",
17 |   },
18 |   {
19 |     content: "Boards",
20 |     value: "board",
21 |   },
22 | ];
23 | 
24 | export default function MenuBarStorybook() {
25 |   const [active, setActive] = useState("all");
26 | 
27 |   return (
28 |     <Section>
29 |       <Inset>
30 |         <Block title="Menu bar">
31 |           <MenuBar items={items} value={active} onChange={setActive} />
32 |         </Block>
33 |       </Inset>
34 |     </Section>
35 |   );
36 | }
37 | 


--------------------------------------------------------------------------------
/src/app/storybook/select/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import Block from "@/components/orbit/block";
 4 | import Inset from "@/components/orbit/inset";
 5 | import Section from "@/components/orbit/section";
 6 | import { Select } from "@/components/orbit/select";
 7 | import { useState } from "react";
 8 | 
 9 | const dbs = [
10 |   { value: "SQLite", label: "SQLite" },
11 |   { value: "MySQL", label: "MySQL" },
12 |   { value: "Postgres", label: "Postgres" },
13 |   { value: "LibSQL", label: "LibSQL" },
14 |   { value: "MongoDB", label: "MongoDB" },
15 |   { value: "Clickhouse", label: "Clickhouse" },
16 |   { value: "BigQuery", label: "BigQuery" },
17 | ];
18 | 
19 | export default function SelectStorybook() {
20 |   const [value, setValue] = useState(dbs[0].value);
21 | 
22 |   return (
23 |     <Section>
24 |       <Inset>
25 |         <Block title="Select">
26 |           <Select
27 |             options={dbs}
28 |             setValue={(value) => setValue(value)}
29 |             value={value}
30 |             size="sm"
31 |           />
32 |           <Select
33 |             options={dbs}
34 |             setValue={(value) => setValue(value)}
35 |             value={value}
36 |             size="base"
37 |           />
38 |           <Select
39 |             options={dbs}
40 |             setValue={(value) => setValue(value)}
41 |             value={value}
42 |             size="lg"
43 |           />
44 |         </Block>
45 |       </Inset>
46 |     </Section>
47 |   );
48 | }
49 | 


--------------------------------------------------------------------------------
/src/app/storybook/server-loading/page.tsx:
--------------------------------------------------------------------------------
 1 | import ServerLoadingAnimation from "@/components/icons/server-loading";
 2 | 
 3 | export default function ServerLoadingStorybook() {
 4 |   return (
 5 |     <div className="p-4">
 6 |       <ServerLoadingAnimation />
 7 |     </div>
 8 |   );
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/app/storybook/toggle/page.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import Block from "@/components/orbit/block";
 4 | import Inset from "@/components/orbit/inset";
 5 | import Section from "@/components/orbit/section";
 6 | import { Toggle } from "@/components/orbit/toggle";
 7 | import { useState } from "react";
 8 | 
 9 | export default function ToggleStorybook() {
10 |   const [toggle, setToggle] = useState(false);
11 | 
12 |   const handleToggleClick = () => {
13 |     setToggle(!toggle);
14 |   };
15 | 
16 |   return (
17 |     <Section>
18 |       <Inset>
19 |         <Block title="Toggle">
20 |           <Toggle onChange={handleToggleClick} toggled={toggle} size="sm" />
21 |           <Toggle onChange={handleToggleClick} toggled={toggle} size="base" />
22 |           <Toggle onChange={handleToggleClick} toggled={toggle} size="lg" />
23 |         </Block>
24 |       </Inset>
25 |     </Section>
26 |   );
27 | }
28 | 


--------------------------------------------------------------------------------
/src/app/storybook/toolbar/example.tsx:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Toolbar,
 3 |   ToolbarButton,
 4 |   ToolbarFiller,
 5 |   ToolbarSeparator,
 6 | } from "@/components/gui/toolbar";
 7 | import { LucideCopy, LucideFolder, LucideSave } from "lucide-react";
 8 | 
 9 | export function StorybookToolbarExample() {
10 |   return (
11 |     <div className="flex h-[300px] max-w-[800px] flex-col border">
12 |       <div className="border-b p-1">
13 |         <Toolbar>
14 |           <ToolbarButton
15 |             text="Save"
16 |             icon={<LucideSave className="h-4 w-4" />}
17 |           />
18 |           <ToolbarButton
19 |             text="Open"
20 |             icon={<LucideFolder className="h-4 w-4" />}
21 |           />
22 |           <ToolbarSeparator />
23 |           <ToolbarButton
24 |             text="Copy"
25 |             icon={<LucideCopy className="h-4 w-4" />}
26 |           />
27 |           <ToolbarFiller />
28 |           <ToolbarButton text="Remove" destructive />
29 |         </Toolbar>
30 |       </div>
31 |       <div className="bg-secondary flex-1"></div>
32 |     </div>
33 |   );
34 | }
35 | 


--------------------------------------------------------------------------------
/src/app/storybook/toolbar/page.mdx:
--------------------------------------------------------------------------------
 1 | import { DocContent } from "@/components/mdx/docs";
 2 | import { StorybookToolbarExample } from "./example";
 3 | 
 4 | export const metadata = {
 5 |   title: "Toolbar",
 6 | };
 7 | 
 8 | <DocContent title="Toolbar" group="Standard Component"></DocContent>
 9 | 
10 | <StorybookToolbarExample />
11 | 


--------------------------------------------------------------------------------
/src/app/styles/markdown.css:
--------------------------------------------------------------------------------
 1 | .mdx-content {
 2 |   @apply leading-7;
 3 | }
 4 | 
 5 | .mdx-content h1 {
 6 |   @apply text-3xl;
 7 | }
 8 | 
 9 | .mdx-content h2 {
10 |   @apply my-4 border-b pb-2 text-2xl font-bold;
11 | }
12 | 
13 | .mdx-content h3 {
14 |   @apply my-4 border-b pb-2 text-xl font-bold;
15 | }
16 | 
17 | .mdx-content ul {
18 |   @apply my-4 list-disc pl-8;
19 | }
20 | 
21 | .mdx-content li ul {
22 |   @apply my-0 list-disc pl-8;
23 | }
24 | 
25 | .mdx-content ol {
26 |   @apply my-4 list-decimal pl-8;
27 | }
28 | 
29 | .mdx-content pre {
30 |   @apply bg-secondary m-0 overflow-x-auto rounded p-4 leading-5;
31 | }
32 | 
33 | .mdx-content p {
34 |   @apply my-2;
35 | }
36 | 
37 | .mdx-content a {
38 |   @apply text-blue-500 underline;
39 | }
40 | 
41 | .mdx-content code {
42 |   @apply bg-secondary rounded p-1;
43 | }
44 | 
45 | .mdx-content pre code {
46 |   @apply rounded-none bg-inherit p-0;
47 | }
48 | 


--------------------------------------------------------------------------------
/src/components/base-handle.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Handle, HandleProps } from "@xyflow/react";
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | export const BaseHandle = React.forwardRef<
 6 |   HTMLDivElement,
 7 |   React.HTMLAttributes<HTMLDivElement> & HandleProps
 8 | >(({ className, ...props }, ref) => (
 9 |   <Handle ref={ref} className={cn("", className)} {...props} />
10 | ));
11 | BaseHandle.displayName = "BaseHandle";
12 | 


--------------------------------------------------------------------------------
/src/components/base-node.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { cn } from "@/lib/utils";
 3 | 
 4 | export const BaseNode = React.forwardRef<
 5 |   HTMLDivElement,
 6 |   React.HTMLAttributes<HTMLDivElement> & { selected?: boolean }
 7 | >(({ className, selected, ...props }, ref) => (
 8 |   <div
 9 |     ref={ref}
10 |     className={cn(
11 |       "rounded-md border bg-card p-5 text-card-foreground",
12 |       className,
13 |       selected ? "border-muted-foreground shadow-lg" : "",
14 |       "hover:ring-1"
15 |     )}
16 |     tabIndex={0}
17 |     {...props}
18 |   />
19 | ));
20 | BaseNode.displayName = "BaseNode";
21 | 


--------------------------------------------------------------------------------
/src/components/board/board-filter/board-filter-input.tsx:
--------------------------------------------------------------------------------
 1 | import { useEffect, useState } from "react";
 2 | 
 3 | interface Props {
 4 |   name: string;
 5 |   value: string;
 6 |   onChange: (v: string) => void;
 7 | }
 8 | 
 9 | export function BoardFilterInput(props: Props) {
10 |   const [internalValue, setInternalValue] = useState(props.value);
11 | 
12 |   useEffect(() => {
13 |     setInternalValue(props.value);
14 |   }, [props.value]);
15 | 
16 |   return (
17 |     <input
18 |       placeholder={`Enter ${props.name}`}
19 |       value={internalValue}
20 |       onChange={(v) => setInternalValue(v.target.value)}
21 |       onBlur={(e) => {
22 |         props.onChange(e.currentTarget.value);
23 |       }}
24 |       className="max-w-14 outline-0"
25 |     />
26 |   );
27 | }
28 | 


--------------------------------------------------------------------------------
/src/components/board/board-sql-error-log.tsx:
--------------------------------------------------------------------------------
 1 | interface BoardSqlErrorLogProps {
 2 |   value: string;
 3 | }
 4 | 
 5 | export default function BoardSqlErrorLog({ value }: BoardSqlErrorLogProps) {
 6 |   return (
 7 |     <div className="mt-2 mb-2 w-full p-2 font-mono text-red-500">{value}</div>
 8 |   );
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/components/board/board-tool/board-title-menu.tsx:
--------------------------------------------------------------------------------
 1 | import { produce } from "immer";
 2 | import { DashboardProps } from "..";
 3 | 
 4 | interface Props {
 5 |   value: DashboardProps;
 6 |   onChange: (v: DashboardProps) => void;
 7 |   onSave: () => void;
 8 | }
 9 | 
10 | export function BoardTitleMenu(props: Props) {
11 |   return (
12 |     <div className="flex items-center">
13 |       <input
14 |         value={props.value.name}
15 |         className="truncate bg-inherit text-center outline-0"
16 |         onChange={(e) =>
17 |           props.onChange(
18 |             produce(props.value, (draft) => {
19 |               draft.name = e.target.value;
20 |             })
21 |           )
22 |         }
23 |         onBlur={props.onSave}
24 |       />
25 |     </div>
26 |   );
27 | }
28 | 


--------------------------------------------------------------------------------
/src/components/board/board-tool/board-tool.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { Button } from "@/components/orbit/button";
 3 | import { ChartLine, ImageUpscale } from "lucide-react";
 4 | import { useBoardContext } from "../board-provider";
 5 | 
 6 | export function BoardTool() {
 7 |   const { boardMode, setBoardMode } = useBoardContext();
 8 | 
 9 |   if (boardMode?.mode === "ADD_CHART") {
10 |     return null;
11 |   }
12 | 
13 |   return (
14 |     <div className="reveal animate-revealMenu sticky bottom-6 mx-auto flex w-[75px] gap-1 rounded-xl bg-neutral-800 p-1 text-neutral-100 shadow-lg dark:bg-white dark:text-neutral-900">
15 |       <Button
16 |         variant="primary"
17 |         shape="square"
18 |         onClick={() => setBoardMode({ mode: "ADD_CHART" })}
19 |       >
20 |         <ChartLine className="h-4 w-4" />
21 |       </Button>
22 | 
23 |       <Button
24 |         shape="square"
25 |         variant="primary"
26 |         onClick={() =>
27 |           setBoardMode(boardMode?.mode ? null : { mode: "REARRANGING_CHART" })
28 |         }
29 |         toggled={boardMode?.mode === "REARRANGING_CHART"}
30 |       >
31 |         <ImageUpscale className="h-4 w-4" />
32 |       </Button>
33 |     </div>
34 |   );
35 | }
36 | 


--------------------------------------------------------------------------------
/src/components/button-group.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | import { PropsWithChildren } from "react";
 3 | 
 4 | export function ButtonGroup({
 5 |   children,
 6 | }: PropsWithChildren<{ suppressHydrationWarning?: boolean }>) {
 7 |   return (
 8 |     <div
 9 |       className={`flex h-9 items-center gap-1 rounded border border-neutral-200 bg-neutral-100 px-1 dark:border-neutral-800 dark:bg-neutral-900`}
10 |     >
11 |       {children}
12 |     </div>
13 |   );
14 | }
15 | 
16 | interface ButtonGroupItemProps {
17 |   onClick?: () => void;
18 |   selected?: boolean;
19 |   suppressHydrationWarning?: boolean;
20 | }
21 | 
22 | export function ButtonGroupItem({
23 |   children,
24 |   selected,
25 |   onClick,
26 | }: PropsWithChildren<ButtonGroupItemProps>) {
27 |   return (
28 |     <button
29 |       onClick={onClick}
30 |       className={cn(
31 |         `flex h-7 cursor-pointer items-center rounded-sm px-2 text-sm text-neutral-600 transition-all hover:bg-neutral-200 dark:text-neutral-400 hover:dark:bg-neutral-800`,
32 |         {
33 |           "bg-neutral-200 dark:bg-neutral-800": selected,
34 |         }
35 |       )}
36 |     >
37 |       {children}
38 |     </button>
39 |   );
40 | }
41 | 


--------------------------------------------------------------------------------
/src/components/chart/chart-background-image.tsx:
--------------------------------------------------------------------------------
 1 | import { produce } from "immer";
 2 | import { Dispatch, SetStateAction } from "react";
 3 | import { ChartValue, outerBaseUrl } from "./chart-type";
 4 | 
 5 | const PRESET_IMAGES = [
 6 |   "/assets/charts/outerbase1.png",
 7 |   "/assets/charts/outerbase2.png",
 8 |   "/assets/charts/outerbase3.png",
 9 |   "/assets/charts/outerbase4.png",
10 |   "/assets/charts/outerbase5.png",
11 |   "/assets/charts/outerbase6.png",
12 | ];
13 | 
14 | interface ChartBackgroundImageProps {
15 |   onChange: Dispatch<SetStateAction<ChartValue>>;
16 | }
17 | 
18 | export default function ChartBackGroundImage({
19 |   onChange,
20 | }: ChartBackgroundImageProps) {
21 |   return (
22 |     <div className="grid grid-cols-3 gap-2 pt-2">
23 |       {PRESET_IMAGES.map((image, index) => {
24 |         return (
25 |           <div key={index} className="relative cursor-pointer">
26 |             <img
27 |               src={outerBaseUrl + image}
28 |               alt=""
29 |               className="h-24 w-full rounded-lg object-cover"
30 |               onClick={() => {
31 |                 onChange((prev) => {
32 |                   return produce(prev, (draft) => {
33 |                     draft.params.options.backgroundImage = image;
34 |                     draft.params.options.backgroundType = "image";
35 |                   });
36 |                 });
37 |               }}
38 |             />
39 |           </div>
40 |         );
41 |       })}
42 |     </div>
43 |   );
44 | }
45 | 


--------------------------------------------------------------------------------
/src/components/chart/chart-type-button.tsx:
--------------------------------------------------------------------------------
 1 | import { Button } from "../orbit/button";
 2 | 
 3 | interface ChartTypeButtonProps {
 4 |   icon: React.ReactNode;
 5 |   isActive: boolean;
 6 |   onClick: () => void;
 7 |   tooltipText: string;
 8 |   suggested: boolean;
 9 | }
10 | 
11 | export function ChartTypeButton({
12 |   tooltipText,
13 |   onClick,
14 |   isActive,
15 |   icon,
16 |   suggested,
17 | }: ChartTypeButtonProps) {
18 |   const className = suggested ? "border border-green-400 rounded-lg" : "";
19 |   return (
20 |     <div className={className}>
21 |       <Button
22 |         variant={isActive ? "primary" : "secondary"}
23 |         size="lg"
24 |         shape="square"
25 |         onClick={onClick}
26 |         title={tooltipText}
27 |       >
28 |         {icon}
29 |       </Button>
30 |     </div>
31 |   );
32 | }
33 | 


--------------------------------------------------------------------------------
/src/components/chart/simple-input.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useEffect } from "react";
 2 | import { Input } from "../orbit/input";
 3 | 
 4 | interface SimpleInputProps {
 5 |   value?: string;
 6 |   placeholder?: string;
 7 |   onSumit: (v: string) => void;
 8 | }
 9 | 
10 | export default function SimpleInput({
11 |   value,
12 |   placeholder,
13 |   onSumit,
14 | }: SimpleInputProps) {
15 |   const [text, setText] = React.useState(value || "");
16 | 
17 |   useEffect(() => {
18 |     setText(value || "");
19 |   }, [value]);
20 | 
21 |   return (
22 |     <Input
23 |       className="h-[36px] w-full rounded-md border p-2"
24 |       placeholder={placeholder ?? ""}
25 |       value={text ?? ""}
26 |       onChange={(v) => {
27 |         setText(v.target.value);
28 |       }}
29 |       size="lg"
30 |       onBlur={() => {
31 |         onSumit(text);
32 |       }}
33 |       onKeyDown={(e) => {
34 |         if (e.key === "Enter") {
35 |           onSumit(text);
36 |         }
37 |       }}
38 |     />
39 |   );
40 | }
41 | 


--------------------------------------------------------------------------------
/src/components/client-only.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { PropsWithChildren, useEffect, useState } from "react";
 3 | 
 4 | export default function ClientOnly(props: PropsWithChildren) {
 5 |   const [clientLoaded, setClientLoaded] = useState(false);
 6 | 
 7 |   useEffect(() => {
 8 |     setClientLoaded(typeof window !== "undefined");
 9 |   }, []);
10 | 
11 |   if (clientLoaded) {
12 |     return props.children;
13 |   }
14 | 
15 |   return null;
16 | }
17 | 


--------------------------------------------------------------------------------
/src/components/connection-config-editor/template/cloudflare.tsx:
--------------------------------------------------------------------------------
 1 | import { ConnectionTemplateList } from "@/app/(outerbase)/base-template";
 2 | import { CommonConnectionConfigTemplate } from "..";
 3 | 
 4 | const template: CommonConnectionConfigTemplate = [
 5 |   {
 6 |     columns: [
 7 |       {
 8 |         name: "username",
 9 |         label: "Account ID",
10 |         type: "text",
11 |         required: true,
12 |         placeholder: "Account ID",
13 |       },
14 |     ],
15 |   },
16 |   {
17 |     columns: [
18 |       {
19 |         name: "database",
20 |         label: "Database ID",
21 |         type: "text",
22 |         required: true,
23 |         placeholder: "Database ID",
24 |       },
25 |     ],
26 |   },
27 |   {
28 |     columns: [
29 |       {
30 |         name: "token",
31 |         label: "API Token",
32 |         type: "textarea",
33 |         required: true,
34 |         placeholder: "API Token",
35 |       },
36 |     ],
37 |   },
38 | ];
39 | 
40 | export const CloudflareConnectionTemplate: ConnectionTemplateList = {
41 |   localFrom: (value) => {
42 |     return {
43 |       name: value.name,
44 |       database: value.database,
45 |       token: value.token,
46 |       username: value.username,
47 |     };
48 |   },
49 |   localTo: (value) => {
50 |     return {
51 |       name: value.name,
52 |       driver: "cloudflare-d1",
53 |       database: value.database,
54 |       token: value.token,
55 |       username: value.username,
56 |     };
57 |   },
58 |   template,
59 |   instruction: <div></div>,
60 | };
61 | 


--------------------------------------------------------------------------------
/src/components/connection-config-editor/template/generic.tsx:
--------------------------------------------------------------------------------
 1 | import { CommonConnectionConfigTemplate } from "..";
 2 | 
 3 | export const GENERIC_CONNECTION_TEMPLATE: CommonConnectionConfigTemplate = [
 4 |   {
 5 |     columns: [
 6 |       {
 7 |         name: "host",
 8 |         label: "Host",
 9 |         type: "text",
10 |         required: true,
11 |         placeholder: "Hostname or IP address",
12 |       },
13 |       {
14 |         name: "port",
15 |         label: "Port",
16 |         type: "text",
17 |         required: true,
18 |         placeholder: "Port",
19 |         size: "max-w-[100px]",
20 |       },
21 |     ],
22 |   },
23 |   {
24 |     columns: [
25 |       {
26 |         name: "username",
27 |         label: "Username",
28 |         type: "text",
29 |         required: true,
30 |         placeholder: "Database username",
31 |       },
32 |       {
33 |         name: "password",
34 |         label: "Password",
35 |         type: "password",
36 |         required: true,
37 |         placeholder: "Database password",
38 |       },
39 |     ],
40 |   },
41 |   {
42 |     columns: [
43 |       {
44 |         name: "database",
45 |         label: "Database",
46 |         type: "text",
47 |         placeholder: "Database name",
48 |       },
49 |     ],
50 |   },
51 |   {
52 |     columns: [
53 |       {
54 |         name: "ssl",
55 |         label: "Use SSL",
56 |         type: "checkbox",
57 |       },
58 |     ],
59 |   },
60 | ];
61 | 


--------------------------------------------------------------------------------
/src/components/connection-config-editor/template/index.ts:
--------------------------------------------------------------------------------
 1 | import { ConnectionTemplateList } from "@/app/(outerbase)/base-template";
 2 | import { CloudflareConnectionTemplate } from "./cloudflare";
 3 | import { CloudflareWAEConnectionTemplate } from "./cloudflare-wae";
 4 | import { MySQLConnectionTemplate } from "./mysql";
 5 | import { PostgresConnectionTemplate } from "./postgres";
 6 | import { RqliteConnectionTemplate } from "./rqlite";
 7 | import { SqliteConnectionTemplate } from "./sqlite";
 8 | import { StarbaseConnectionTemplate } from "./starbase";
 9 | import { TursoConnectionTemplate } from "./turso";
10 | import { ValtownConnectionTemplate } from "./valtown";
11 | 
12 | export const ConnectionTemplateDictionary: Record<
13 |   string,
14 |   ConnectionTemplateList
15 | > = {
16 |   "cloudflare-wae": CloudflareWAEConnectionTemplate,
17 |   "cloudflare-d1": CloudflareConnectionTemplate,
18 |   rqlite: RqliteConnectionTemplate,
19 |   "sqlite-filehandler": SqliteConnectionTemplate,
20 |   turso: TursoConnectionTemplate,
21 |   valtown: ValtownConnectionTemplate,
22 | 
23 |   starbase: StarbaseConnectionTemplate,
24 |   starbasedb: StarbaseConnectionTemplate,
25 | 
26 |   mysql: MySQLConnectionTemplate,
27 |   postgres: PostgresConnectionTemplate,
28 | };
29 | 


--------------------------------------------------------------------------------
/src/components/connection-config-editor/template/mysql.tsx:
--------------------------------------------------------------------------------
 1 | import { ConnectionTemplateList } from "@/app/(outerbase)/base-template";
 2 | import { GENERIC_CONNECTION_TEMPLATE } from "./generic";
 3 | 
 4 | export const MySQLConnectionTemplate: ConnectionTemplateList = {
 5 |   template: GENERIC_CONNECTION_TEMPLATE,
 6 |   remoteFrom: (value) => {
 7 |     return {
 8 |       name: value.name,
 9 |       host: value.source.host,
10 |       username: value.source.user,
11 |       database: value.source.database,
12 |       port: value.source.port,
13 |     };
14 |   },
15 |   remoteTo: (value) => {
16 |     return {
17 |       name: value.name,
18 |       source: {
19 |         host: value.host,
20 |         user: value.username,
21 |         password: value.password,
22 |         database: value.database,
23 |         port: value.port,
24 |         type: "mysql",
25 |         base_id: "",
26 |       },
27 |     };
28 |   },
29 | };
30 | 


--------------------------------------------------------------------------------
/src/components/connection-config-editor/template/postgres.tsx:
--------------------------------------------------------------------------------
 1 | import { ConnectionTemplateList } from "@/app/(outerbase)/base-template";
 2 | import { GENERIC_CONNECTION_TEMPLATE } from "./generic";
 3 | 
 4 | export const PostgresConnectionTemplate: ConnectionTemplateList = {
 5 |   template: GENERIC_CONNECTION_TEMPLATE,
 6 |   remoteFrom: (value) => {
 7 |     return {
 8 |       name: value.name,
 9 |       host: value.source.host,
10 |       username: value.source.user,
11 |       database: value.source.database,
12 |       port: value.source.port,
13 |     };
14 |   },
15 |   remoteTo: (value) => {
16 |     return {
17 |       name: value.name,
18 |       source: {
19 |         host: value.host,
20 |         user: value.username,
21 |         password: value.password,
22 |         database: value.database,
23 |         port: value.port,
24 |         type: "postgres",
25 |         base_id: "",
26 |       },
27 |     };
28 |   },
29 | };
30 | 


--------------------------------------------------------------------------------
/src/components/connection-config-editor/template/sqlite.tsx:
--------------------------------------------------------------------------------
 1 | import { ConnectionTemplateList } from "@/app/(outerbase)/base-template";
 2 | import { CommonConnectionConfigTemplate } from "..";
 3 | 
 4 | const template: CommonConnectionConfigTemplate = [
 5 |   {
 6 |     columns: [
 7 |       {
 8 |         name: "filehandler",
 9 |         label: "File",
10 |         type: "file",
11 |         required: true,
12 |         placeholder: "Please select file",
13 |       },
14 |     ],
15 |   },
16 | ];
17 | 
18 | export const SqliteConnectionTemplate: ConnectionTemplateList = {
19 |   template,
20 |   localFrom: (value) => {
21 |     return {
22 |       name: value.name,
23 |       filehandler: value.file_handler,
24 |     };
25 |   },
26 |   localTo: (value) => {
27 |     return {
28 |       name: value.name,
29 |       driver: "sqlite-filehandler",
30 |       file_handler: value.filehandler,
31 |     };
32 |   },
33 | };
34 | 


--------------------------------------------------------------------------------
/src/components/copyable-text.tsx:
--------------------------------------------------------------------------------
 1 | import { Copy } from "@phosphor-icons/react";
 2 | 
 3 | interface CopyableTextProps {
 4 |   text: string;
 5 | }
 6 | 
 7 | export default function CopyableText({ text }: CopyableTextProps) {
 8 |   return (
 9 |     <span
10 |       className="bg-secondary inline-flex cursor-pointer items-center p-1 px-2 font-mono"
11 |       onClick={() => navigator.clipboard.writeText(text)}
12 |     >
13 |       {text} <Copy className="ml-2 h-4 w-4" />
14 |     </span>
15 |   );
16 | }
17 | 


--------------------------------------------------------------------------------
/src/components/editor/prompt-plugin.css:
--------------------------------------------------------------------------------
 1 | .prompt-line-preview {
 2 |   background: #abf7b1;
 3 | }
 4 | 
 5 | .prompt-line-selected {
 6 |   background: #f5f5f5;
 7 | }
 8 | 
 9 | .dark .prompt-line-selected {
10 |   background: #222;
11 | }
12 | 
13 | .cm-deletedChunk {
14 |   background-color: #fab1a0 !important;
15 | }
16 | 
17 | .dark .cm-deletedChunk {
18 |   background-color: #b54b4b !important;
19 | }
20 | 
21 | .cm-changedLine {
22 |   background-color: #55efc4 !important;
23 | }
24 | 
25 | .cm-changedLine {
26 |   background-color: #55efc4 !important;
27 | }
28 | 
29 | .dark .cm-changedLine {
30 |   background-color: #2f6542 !important;
31 | }
32 | 
33 | .cm-insertedLine .cm-changedText {
34 |   background: none !important;
35 | }
36 | 
37 | .cm-deletedChunk .cm-deletedText {
38 |   background: none !important;
39 | }
40 | 
41 | .cm-deletedChunk del {
42 |   text-decoration: none !important;
43 | }
44 | 


--------------------------------------------------------------------------------
/src/components/gui/code-preview/index.tsx:
--------------------------------------------------------------------------------
1 | export default function CodePreview({ code }: { code: string }) {
2 |   return (
3 |     <code className="p-2 bg-secondary block overflow-x-auto w-full text-sm">
4 |       <pre>{code}</pre>
5 |     </code>
6 |   );
7 | }
8 | 


--------------------------------------------------------------------------------
/src/components/gui/custom/ErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | export default function ErrorMessage({
2 |   message,
3 | }: {
4 |   readonly message: string;
5 | }) {
6 |   return <div className="text-xs text-red-500">{message}</div>;
7 | }
8 | 


--------------------------------------------------------------------------------
/src/components/gui/list-button-item.tsx:
--------------------------------------------------------------------------------
 1 | import { buttonVariants } from "../ui/button";
 2 | import { Icon } from "@phosphor-icons/react";
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | export default function ListButtonItem({
 6 |   selected,
 7 |   text,
 8 |   icon: Icon,
 9 |   onClick,
10 | }: Readonly<{
11 |   selected?: boolean;
12 |   text: string;
13 |   icon?: Icon;
14 |   onClick: () => void;
15 | }>) {
16 |   return (
17 |     <button
18 |       onClick={onClick}
19 |       className={cn(
20 |         buttonVariants({
21 |           variant: selected ? "default" : "ghost",
22 |           size: "sm",
23 |         }),
24 |         "justify-start",
25 |         "cursor-pointer"
26 |       )}
27 |     >
28 |       {Icon ? (
29 |         <Icon className="w-4 h-4 mr-2" />
30 |       ) : (
31 |         <div className="w-4 h-4 mr-2"></div>
32 |       )}
33 |       {text}
34 |     </button>
35 |   );
36 | }
37 | 


--------------------------------------------------------------------------------
/src/components/gui/loading-opacity.tsx:
--------------------------------------------------------------------------------
 1 | import { LucideLoader2 } from "lucide-react";
 2 | 
 3 | export default function OpacityLoading() {
 4 |   return (
 5 |     <div className="absolute left-0 right-0 top-0 bottom-0 z-10">
 6 |       <div className="absolute left-0 right-0 top-0 bottom-0 opacity-50 bg-gray-600" />
 7 |       <div className="absolute left-0 right-0 top-0 bottom-0 flex justify-center items-center">
 8 |         <div className="p-5 bg-white rounded-lg justify-center items-center flex flex-col">
 9 |           <LucideLoader2 className="animate-spin text-2xl mb-2" />
10 |           <div className="text-sm">Loading</div>
11 |         </div>
12 |       </div>
13 |     </div>
14 |   );
15 | }
16 | 


--------------------------------------------------------------------------------
/src/components/gui/logo-loading.tsx:
--------------------------------------------------------------------------------
 1 | export default function LogoLoading() {
 2 |   return (
 3 |     <div className="flex gap-2 items-center">
 4 |       <div className="w-12 h-12 bg-black dark:bg-white items-center justify-center flex rounded-lg text-white dark:text-black">
 5 |         <svg
 6 |           className="w-8 h-8"
 7 |           width="32"
 8 |           height="33"
 9 |           viewBox="0 0 32 33"
10 |           xmlns="http://www.w3.org/2000/svg"
11 |         >
12 |           <path
13 |             d="M16 0.400024C7.17638 0.400024 0 7.57335 0 16.3999C0 25.2182 7.17638 32.4 16 32.4C24.8235 32.4 32 25.2267 32 16.3999C32 7.57335 24.8235 0.400024 16 0.400024ZM21.9476 27.4141L21.8674 27.5173C21.0305 28.5421 20.0066 28.878 19.2942 28.9727C19.1073 28.9985 18.9203 29.0072 18.7245 29.0072C16.7835 29.0072 14.8247 27.6896 13.0617 25.1836C11.6282 23.1427 10.3817 20.4216 9.55368 17.5194C8.08456 12.3439 8.25373 7.62496 9.98105 5.48938C10.818 4.46456 11.8419 4.12871 12.5542 4.03403C14.5754 3.75832 16.641 4.93816 18.5108 7.4354C20.0599 9.50231 21.3956 12.3527 22.2861 15.4785C23.7373 20.5766 23.5948 25.244 21.9476 27.4141Z"
14 |             fill="currentColor"
15 |           />
16 |         </svg>
17 |       </div>
18 | 
19 |       <div>
20 |         <h1 className="text-2xl font-semibold">LibSQL Studio</h1>
21 |       </div>
22 |     </div>
23 |   );
24 | }
25 | 


--------------------------------------------------------------------------------
/src/components/gui/main-connection.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { TooltipProvider } from "@/components/ui/tooltip";
 3 | import { WEBSITE_NAME } from "@/const";
 4 | import { AutoCompleteProvider } from "@/context/auto-complete-provider";
 5 | import { useStudioContext } from "@/context/driver-provider";
 6 | import { SchemaProvider } from "@/context/schema-provider";
 7 | import { useEffect } from "react";
 8 | import { DialogProvider } from "../create-dialog";
 9 | import ContextMenuHandler from "./context-menu-handler";
10 | import DatabaseGui from "./database-gui";
11 | 
12 | function MainConnection() {
13 |   const { databaseDriver: driver } = useStudioContext();
14 | 
15 |   useEffect(() => {
16 |     return () => {
17 |       driver.close();
18 |     };
19 |   }, [driver]);
20 | 
21 |   return (
22 |     <SchemaProvider>
23 |       <DatabaseGui />
24 |       <DialogProvider slot="base" />
25 |     </SchemaProvider>
26 |   );
27 | }
28 | 
29 | function MainConnectionContainer() {
30 |   const { name } = useStudioContext();
31 | 
32 |   useEffect(() => {
33 |     document.title = name + " - " + WEBSITE_NAME;
34 |   }, [name]);
35 | 
36 |   return (
37 |     <>
38 |       <AutoCompleteProvider>
39 |         <TooltipProvider>
40 |           <MainConnection />
41 |         </TooltipProvider>
42 |       </AutoCompleteProvider>
43 |       <ContextMenuHandler />
44 |     </>
45 |   );
46 | }
47 | 
48 | export default function MainScreen() {
49 |   return <MainConnectionContainer />;
50 | }
51 | 


--------------------------------------------------------------------------------
/src/components/gui/query-explanation-diagram/node-type/tooltip-handle.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import {
 3 |   Tooltip,
 4 |   TooltipContent,
 5 |   TooltipTrigger,
 6 | } from "@/components/ui/tooltip";
 7 | 
 8 | export function TooltipExplainHandle(props: {
 9 |   children: React.ReactNode;
10 |   content: React.ReactNode;
11 |   disabled?: boolean;
12 | }) {
13 |   if (props.disabled === true) {
14 |     return <>{props.children}</>;
15 |   }
16 | 
17 |   return (
18 |     <Tooltip>
19 |       <TooltipTrigger asChild>{props.children}</TooltipTrigger>
20 |       <TooltipContent>{props.content}</TooltipContent>
21 |     </Tooltip>
22 |   );
23 | }
24 | 


--------------------------------------------------------------------------------
/src/components/gui/query-explanation-diagram/node-type/union-block.tsx:
--------------------------------------------------------------------------------
 1 | import { BaseHandle } from "@/components/base-handle";
 2 | import { ExplainNodeProps } from "../build-query-explanation-flow";
 3 | import { Position } from "@xyflow/react";
 4 | 
 5 | export function UnionBlock(props: ExplainNodeProps) {
 6 |   return (
 7 |     <div>
 8 |       <BaseHandle
 9 |         type="source"
10 |         position={Position.Right}
11 |         id={props.id}
12 |         className="opacity-0 group-hover:opacity-100 w-[10px]! h-[10px]!"
13 |       />
14 |       <BaseHandle
15 |         type="target"
16 |         position={Position.Left}
17 |         id={props.id}
18 |         className="opacity-0 group-hover:opacity-100 w-[10px]! h-[10px]!"
19 |       />
20 |       <div className="flex flex-row justify-center max-w-[200px]  p-2 w-[150px] items-center bg-gray-300 text-gray-900 border-gray-900 text-[8pt]">
21 |         <small>{props.data.label}</small>
22 |       </div>
23 |     </div>
24 |   );
25 | }
26 | 


--------------------------------------------------------------------------------
/src/components/gui/result-stat.tsx:
--------------------------------------------------------------------------------
 1 | import { DatabaseResultStat } from "@/drivers/base-driver";
 2 | 
 3 | export default function ResultStats({ stats }: { stats: DatabaseResultStat }) {
 4 |   return (
 5 |     <div className="text-sm p-2 flex">
 6 |       {stats.queryDurationMs !== null && (
 7 |         <div className="px-2 border-r">
 8 |           <span className="font-semibold">Query Duration</span>:{" "}
 9 |           {stats.queryDurationMs}ms
10 |         </div>
11 |       )}
12 | 
13 |       {!!stats.rowsRead && (
14 |         <div className="px-2 border-r">
15 |           <span className="font-semibold">Rows Read</span>: {stats.rowsRead}
16 |         </div>
17 |       )}
18 | 
19 |       {!!stats.rowsWritten && (
20 |         <div className="px-2 border-r">
21 |           <span className="font-semibold">Rows Written</span>:{" "}
22 |           {stats.rowsWritten}
23 |         </div>
24 |       )}
25 | 
26 |       {!!stats.rowsAffected && (
27 |         <div className="px-2">
28 |           <span className="font-semibold">Affected Rows</span>:{" "}
29 |           {stats.rowsAffected}
30 |         </div>
31 |       )}
32 |     </div>
33 |   );
34 | }
35 | 


--------------------------------------------------------------------------------
/src/components/gui/schema-editor/column-collation.tsx:
--------------------------------------------------------------------------------
 1 | export default function ColumnCollation({
 2 |   value,
 3 |   onChange,
 4 |   disabled,
 5 | }: {
 6 |   value?: string;
 7 |   onChange: (value: string) => void;
 8 |   disabled?: boolean;
 9 | }) {
10 |   return (
11 |     <input
12 |       value={value || ""}
13 |       placeholder="Collation"
14 |       disabled={disabled}
15 |       list="collation-list"
16 |       className="p-2 text-xs outline-hidden w-[150px] bg-inherit"
17 |       spellCheck={false}
18 |       onChange={(e) => onChange(e.currentTarget.value)}
19 |     />
20 |   );
21 | }
22 | 


--------------------------------------------------------------------------------
/src/components/gui/schema-editor/column-conflict-clause.tsx:
--------------------------------------------------------------------------------
 1 | import { DatabaseColumnConflict } from "@/drivers/base-driver";
 2 | import {
 3 |   Select,
 4 |   SelectContent,
 5 |   SelectItem,
 6 |   SelectTrigger,
 7 |   SelectValue,
 8 | } from "../../ui/select";
 9 | 
10 | export default function ConflictClauseOptions({
11 |   value,
12 |   onChange,
13 |   disabled,
14 | }: Readonly<{
15 |   value?: DatabaseColumnConflict;
16 |   onChange?: (v: DatabaseColumnConflict) => void;
17 |   disabled?: boolean;
18 | }>) {
19 |   return (
20 |     <Select value={value} onValueChange={onChange}>
21 |       <SelectTrigger className="bg-background" disabled={disabled}>
22 |         <SelectValue placeholder="Conflict" />
23 |       </SelectTrigger>
24 |       <SelectContent>
25 |         <SelectItem value="ROLLBACK">Rollback</SelectItem>
26 |         <SelectItem value="ABORT">Abort</SelectItem>
27 |         <SelectItem value="FAIL">Fail</SelectItem>
28 |         <SelectItem value="IGNORE">Ignore</SelectItem>
29 |         <SelectItem value="REPLACE">Replace</SelectItem>
30 |       </SelectContent>
31 |     </Select>
32 |   );
33 | }
34 | 


--------------------------------------------------------------------------------
/src/components/gui/schema-editor/column-provider.tsx:
--------------------------------------------------------------------------------
 1 | import { DatabaseTableColumnChange } from "@/drivers/base-driver";
 2 | import { PropsWithChildren, createContext, useContext } from "react";
 3 | 
 4 | const ColumnContext = createContext<{ columns: DatabaseTableColumnChange[] }>({
 5 |   columns: [],
 6 | });
 7 | 
 8 | export function useColumnList() {
 9 |   return useContext(ColumnContext);
10 | }
11 | 
12 | export function ColumnsProvider({
13 |   children,
14 |   value,
15 | }: PropsWithChildren<{ value: DatabaseTableColumnChange[] }>) {
16 |   return (
17 |     <ColumnContext.Provider value={{ columns: value }}>
18 |       {children}
19 |     </ColumnContext.Provider>
20 |   );
21 | }
22 | 


--------------------------------------------------------------------------------
/src/components/gui/schema-editor/schema-create/index.tsx:
--------------------------------------------------------------------------------
 1 | import { Dialog, DialogContent, DialogHeader } from "@/components/ui/dialog";
 2 | import React from "react";
 3 | import { SchemaDatabaseCreateForm } from "./schema-create-form";
 4 | 
 5 | interface Props {
 6 |   schemaName?: string;
 7 |   onClose: () => void
 8 | }
 9 | 
10 | export default function SchemaCreateDialog(props: React.PropsWithChildren<Props>) {
11 |   return (
12 |     <Dialog defaultOpen onOpenChange={props.onClose}>
13 |       <DialogContent>
14 |         <DialogHeader>
15 |           {!props.schemaName ? 'New Schema/Database' : props.schemaName + "-Schema"}
16 |         </DialogHeader>
17 |         <SchemaDatabaseCreateForm schemaName={props.schemaName} onClose={props.onClose} />
18 |       </DialogContent>
19 |     </Dialog>
20 |   )
21 | }


--------------------------------------------------------------------------------
/src/components/gui/sidebar/tools-sidebar.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | import { scc } from "@/core/command";
 3 | import ListButtonItem from "../list-button-item";
 4 | import { StackMinus, TreeStructure } from "@phosphor-icons/react";
 5 | 
 6 | export default function SettingSidebar() {
 7 |   return (
 8 |     <div className="flex flex-col grow p-2">
 9 |       <ListButtonItem
10 |         text="Relational Diagram"
11 |         onClick={() => {
12 |           scc.tabs.openBuiltinERD({});
13 |         }}
14 |         icon={TreeStructure}
15 |       />
16 |       <ListButtonItem
17 |         text="Drop & Empty Multiple Tables"
18 |         onClick={() => {
19 |           scc.tabs.openBuiltinMassDropTable({});
20 |         }}
21 |         icon={StackMinus}
22 |       />
23 |     </div>
24 |   );
25 | }
26 | 


--------------------------------------------------------------------------------
/src/components/gui/table-cell/big-number-cell.tsx:
--------------------------------------------------------------------------------
 1 | import createEditableCell from "./create-editable-cell";
 2 | 
 3 | const BigNumberCell = createEditableCell<bigint>({
 4 |   align: "right",
 5 |   toString: (v) => {
 6 |     if (v === null) return null;
 7 |     if (v === undefined) return undefined;
 8 |     return v.toString();
 9 |   },
10 |   toValue: (v) => {
11 |     if (v === null) return null;
12 |     if (v === undefined) return undefined;
13 |     if (v === "") return null;
14 | 
15 |     try {
16 |       return BigInt(v);
17 |     } catch {
18 |       return null;
19 |     }
20 |   },
21 | });
22 | 
23 | export default BigNumberCell;
24 | 


--------------------------------------------------------------------------------
/src/components/gui/table-cell/blob-cell.tsx:
--------------------------------------------------------------------------------
 1 | import { prettifyBytes } from "@/components/gui/table-cell/generic-cell";
 2 | import createEditableCell from "./create-editable-cell";
 3 | 
 4 | const BlobCell = createEditableCell<number[]>({
 5 |   toString: (v) => {
 6 |     if (v === null) return null;
 7 |     if (v === undefined) return undefined;
 8 | 
 9 |     return prettifyBytes(Uint8Array.from(v ?? []));
10 |   },
11 |   toValue: (v) => {
12 |     if (v === null) return null;
13 |     if (v === undefined) return undefined;
14 |     if (v === "") return null;
15 | 
16 |     return [...v.matchAll(/(\\\\)|\\x([0-9a-f]{2})|(.)/gi)].map(
17 |       ([, escape, hex, letter]) =>
18 |         escape !== undefined
19 |           ? 0x5c
20 |           : letter !== undefined
21 |             ? letter.codePointAt(0)
22 |             : parseInt(hex, 16)
23 |     ) as number[];
24 |   },
25 | });
26 | 
27 | export default BlobCell;
28 | 


--------------------------------------------------------------------------------
/src/components/gui/table-cell/number-cell.tsx:
--------------------------------------------------------------------------------
 1 | import createEditableCell from "./create-editable-cell";
 2 | 
 3 | const NumberCell = createEditableCell<number>({
 4 |   align: "right",
 5 |   toString: (v) => {
 6 |     if (v === null) return null;
 7 |     if (v === undefined) return undefined;
 8 |     return v.toString();
 9 |   },
10 |   toValue: (v) => {
11 |     if (v === null) return null;
12 |     if (v === undefined) return undefined;
13 |     if (v === "") return null;
14 | 
15 |     const parsedNumber = Number(v);
16 |     if (Number.isFinite(parsedNumber)) {
17 |       return parsedNumber;
18 |     }
19 |     return null;
20 |   },
21 | });
22 | 
23 | export default NumberCell;
24 | 


--------------------------------------------------------------------------------
/src/components/gui/table-cell/styles.module.css:
--------------------------------------------------------------------------------
 1 | .cell {
 2 |   line-height: 34px;
 3 |   height: 35px;
 4 |   border: 1px solid transparent;
 5 | }
 6 | 
 7 | .focus {
 8 |   border: 1px solid #e00;
 9 | }
10 | 
11 | :global(.dark) .change {
12 |   @apply bg-yellow-500;
13 | }
14 | 
15 | .change {
16 |   @apply bg-yellow-200;
17 | }
18 | 


--------------------------------------------------------------------------------
/src/components/gui/table-cell/text-cell.tsx:
--------------------------------------------------------------------------------
1 | import createEditableCell from "./create-editable-cell";
2 | 
3 | const TextCell = createEditableCell<string>({
4 |   toString: (v) => v,
5 |   toValue: (v) => v,
6 | });
7 | 
8 | export default TextCell;
9 | 


--------------------------------------------------------------------------------
/src/components/gui/table-optimized/helper.ts:
--------------------------------------------------------------------------------
 1 | import OptimizeTableState from "./optimize-table-state";
 2 | 
 3 | export function createSimpleTableState(
 4 |   headers: string[],
 5 |   data: Record<string, unknown>[]
 6 | ) {
 7 |   return new OptimizeTableState(
 8 |     headers.map((header) => ({
 9 |       name: header,
10 |       display: {
11 |         initialSize: 150,
12 |         text: header,
13 |       },
14 |       sticky: false,
15 |       metadata: {
16 |         isPrimaryKey: false,
17 |       },
18 |       setting: {
19 |         readonly: true,
20 |         resizable: true,
21 |       },
22 |       store: new Map(),
23 |     })),
24 |     data
25 |   );
26 | }
27 | 


--------------------------------------------------------------------------------
/src/components/gui/table-optimized/table-fake-body-padding.tsx:
--------------------------------------------------------------------------------
 1 | import { PropsWithChildren } from "react";
 2 | 
 3 | export default function TableFakeBodyPadding({
 4 |   children,
 5 |   colCount,
 6 |   rowHeight,
 7 |   rowCount,
 8 |   rowStart,
 9 |   rowEnd,
10 | }: PropsWithChildren<{
11 |   rowHeight: number;
12 |   colCount: number;
13 |   rowCount: number;
14 |   rowStart: number;
15 |   rowEnd: number;
16 | }>) {
17 |   const paddingTop = rowStart * rowHeight;
18 |   const paddingBottom = (rowCount - rowEnd) * rowHeight;
19 | 
20 |   return (
21 |     <tbody className="contents">
22 |       {!!paddingTop && (
23 |         <tr key="padding-top" className="contents">
24 |           <td
25 |             style={{
26 |               height: paddingTop,
27 |               gridColumn: `span ${colCount + 1}`,
28 |             }}
29 |           />
30 |         </tr>
31 |       )}
32 | 
33 |       {children}
34 | 
35 |       {!!paddingBottom && (
36 |         <tr className="contents" key="padding-bottom">
37 |           <td
38 |             style={{
39 |               height: paddingBottom,
40 |               gridColumn: `span ${colCount + 1}`,
41 |             }}
42 |           ></td>
43 |         </tr>
44 |       )}
45 |     </tbody>
46 |   );
47 | }
48 | 


--------------------------------------------------------------------------------
/src/components/gui/table-optimized/table-fake-row-padding.tsx:
--------------------------------------------------------------------------------
 1 | export default function TableFakeRowPadding({
 2 |   colStart,
 3 |   colEnd,
 4 | }: {
 5 |   colEnd: number;
 6 |   colStart: number;
 7 | }) {
 8 |   return colEnd - colStart > 0 ? (
 9 |     <td
10 |       style={{
11 |         gridColumn: `span ${colEnd - colStart}`,
12 |       }}
13 |     />
14 |   ) : (
15 |     <></>
16 |   );
17 | }
18 | 


--------------------------------------------------------------------------------
/src/components/gui/table-optimized/table-header.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | import React, { type ReactElement } from "react";
 3 | import type { OptimizeTableHeaderWithIndexProps } from ".";
 4 | import OptimizeTableState from "./optimize-table-state";
 5 | import TableHeaderResizeHandler from "./table-header-resize-handler";
 6 | 
 7 | export default function TableHeader<HeaderMetadata = unknown>({
 8 |   idx,
 9 |   header,
10 |   onHeaderResize,
11 |   onContextMenu,
12 |   sticky,
13 |   renderHeader,
14 |   state,
15 | }: {
16 |   idx: number;
17 |   sticky: boolean;
18 |   header: OptimizeTableHeaderWithIndexProps<HeaderMetadata>;
19 |   state: OptimizeTableState<HeaderMetadata>;
20 |   onHeaderResize: (idx: number, newWidth: number) => void;
21 |   onContextMenu?: React.MouseEventHandler;
22 |   renderHeader: (
23 |     props: OptimizeTableHeaderWithIndexProps<HeaderMetadata>
24 |   ) => ReactElement;
25 | }) {
26 |   const className = cn(
27 |     sticky ? "z-30" : "z-10",
28 |     "bg-background border-r border-b overflow-hidden sticky top-0 h-[35px] leading-[35px] flex text-left p-0"
29 |   );
30 | 
31 |   return (
32 |     <th
33 |       key={header.name}
34 |       title={header.display.tooltip}
35 |       className={className}
36 |       onContextMenu={onContextMenu}
37 |       style={{
38 |         left: sticky ? state.gutterColumnWidth : undefined,
39 |       }}
40 |     >
41 |       {renderHeader(header)}
42 |       {header.setting.resizable && (
43 |         <TableHeaderResizeHandler idx={idx + 1} onResize={onHeaderResize} />
44 |       )}
45 |     </th>
46 |   );
47 | }
48 | 


--------------------------------------------------------------------------------
/src/components/gui/table-result/helper.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   buildTableResultHeader,
 3 |   BuildTableResultProps,
 4 | } from "@/lib/build-table-result";
 5 | import OptimizeTableState from "../table-optimized/optimize-table-state";
 6 | import { TableHeaderMetadata } from "./type";
 7 | 
 8 | export function createTableStateFromResult(props: BuildTableResultProps) {
 9 |   const r = new OptimizeTableState<TableHeaderMetadata>(
10 |     buildTableResultHeader(props),
11 |     props.result.rows.map((r) => ({ ...r }))
12 |   );
13 | 
14 |   if (r.getRowsCount() >= 1000) {
15 |     r.gutterColumnWidth = 50;
16 |   }
17 | 
18 |   if (r.getRowsCount() >= 10000) {
19 |     r.gutterColumnWidth = 60;
20 |   }
21 | 
22 |   return r;
23 | }
24 | 


--------------------------------------------------------------------------------
/src/components/gui/table-result/type.tsx:
--------------------------------------------------------------------------------
 1 | import { DatabaseTableColumn } from "@/drivers/base-driver";
 2 | import { ColumnType } from "@outerbase/sdk-transform";
 3 | 
 4 | export interface TableHeaderMetadata {
 5 |   from?: {
 6 |     schema: string;
 7 |     table: string;
 8 |     column: string;
 9 |   };
10 | 
11 |   // Primary key
12 |   isPrimaryKey: boolean;
13 | 
14 |   // Foreign key reference
15 |   referenceTo?: {
16 |     schema: string;
17 |     table: string;
18 |     column: string;
19 |   };
20 | 
21 |   type?: ColumnType;
22 |   originalType?: string;
23 | 
24 |   columnSchema?: DatabaseTableColumn;
25 | }
26 | 


--------------------------------------------------------------------------------
/src/components/gui/tabs-result/explain-result-tab.tsx:
--------------------------------------------------------------------------------
 1 | import { useStudioContext } from "@/context/driver-provider";
 2 | import { DatabaseResultSet } from "@/drivers/base-driver";
 3 | import { QueryExplanation } from "../query-explanation";
 4 | 
 5 | export default function ExplainResultTab({
 6 |   data,
 7 | }: {
 8 |   data: DatabaseResultSet;
 9 | }) {
10 |   const { databaseDriver } = useStudioContext();
11 |   return (
12 |     <div className="flex h-full w-full flex-col border-t">
13 |       <div className="grow overflow-hidden">
14 |         <QueryExplanation
15 |           data={data}
16 |           dialect={databaseDriver.getFlags().dialect}
17 |         />
18 |       </div>
19 |     </div>
20 |   );
21 | }
22 | 


--------------------------------------------------------------------------------
/src/components/gui/tabs/relational-diagram-tab/context-menu-diagram.tsx:
--------------------------------------------------------------------------------
 1 | import { useStudioContext } from "@/context/driver-provider";
 2 | import { PropsWithChildren } from "react";
 3 | 
 4 | import { scc } from "@/core/command";
 5 | import {
 6 |   ContextMenu,
 7 |   ContextMenuContent,
 8 |   ContextMenuItem,
 9 |   ContextMenuTrigger,
10 | } from "../../../ui/context-menu";
11 | 
12 | export default function ContextMenuERD({
13 |   schemaName,
14 |   tableName,
15 |   children,
16 | }: PropsWithChildren<{ schemaName: string; tableName: string }>) {
17 |   const { databaseDriver } = useStudioContext();
18 | 
19 |   const handleEditTable = () => {
20 |     scc.tabs.openBuiltinSchema({
21 |       schemaName: schemaName,
22 |       tableName: tableName,
23 |     });
24 |   };
25 | 
26 |   const handleOpenTableData = () => {
27 |     scc.tabs.openBuiltinTable({ tableName: tableName, schemaName: schemaName });
28 |   };
29 | 
30 |   return (
31 |     <ContextMenu>
32 |       <ContextMenuTrigger>{children}</ContextMenuTrigger>
33 |       <ContextMenuContent>
34 |         <ContextMenuItem onClick={handleOpenTableData}>
35 |           Expore Table Data
36 |         </ContextMenuItem>
37 |         {databaseDriver.getFlags().supportCreateUpdateTable && (
38 |           <ContextMenuItem onClick={handleEditTable}>
39 |             Edit Table
40 |           </ContextMenuItem>
41 |         )}
42 |       </ContextMenuContent>
43 |     </ContextMenu>
44 |   );
45 | }
46 | 


--------------------------------------------------------------------------------
/src/components/gui/tabs/relational-diagram-tab/database-schema-node.tsx:
--------------------------------------------------------------------------------
 1 | import { BaseNode } from "@/components/base-node";
 2 | import { TableBody } from "@/components/ui/table";
 3 | import { Node, NodeProps } from "@xyflow/react";
 4 | import ContextMenuERD from "./context-menu-diagram";
 5 | import ERDTableColumn from "./erd-table-column";
 6 | 
 7 | export interface ERDSchemaNodeColumnProps {
 8 |   title: string;
 9 |   type: string;
10 |   pk: boolean;
11 |   fk: boolean;
12 |   unique: boolean;
13 | }
14 | 
15 | export type ERDSchemaNodeProps = Node<{
16 |   label: string;
17 |   schemaName: string;
18 |   schema: ERDSchemaNodeColumnProps[];
19 | }>;
20 | 
21 | export function DatabaseSchemaNode({
22 |   data,
23 |   selected,
24 | }: NodeProps<ERDSchemaNodeProps>) {
25 |   const schema = data.schema;
26 | 
27 |   return (
28 |     <BaseNode className="p-0" selected={selected}>
29 |       <ContextMenuERD tableName={data.label} schemaName={data.schemaName}>
30 |         <h2 className="bg-secondary text-muted-foreground flex h-[30px] max-w-[300px] items-center justify-center rounded-tl-md rounded-tr-md p-2 text-sm transition-all hover:text-blue-600">
31 |           {data.label}
32 |         </h2>
33 |       </ContextMenuERD>
34 |       {/* shadcn Table cannot be used because of hardcoded overflow-auto */}
35 |       <table className="w-full overflow-visible">
36 |         <TableBody>
37 |           {schema.map((entry) => {
38 |             return <ERDTableColumn key={entry.title} column={entry} />;
39 |           })}
40 |         </TableBody>
41 |       </table>
42 |     </BaseNode>
43 |   );
44 | }
45 | 


--------------------------------------------------------------------------------
/src/components/hooks/useElementResize.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect } from "react";
 2 | 
 3 | export default function useElementResize<T extends Element = Element>(
 4 |   callback: (element: T) => void,
 5 |   ref: React.RefObject<T>
 6 | ) {
 7 |   useEffect(() => {
 8 |     if (ref.current) {
 9 |       callback(ref.current);
10 |     }
11 |   }, [ref, callback]);
12 | 
13 |   useEffect(() => {
14 |     if (ref.current) {
15 |       const resizeObserver = new ResizeObserver((entries) => {
16 |         for (const entry of entries) {
17 |           callback(entry.target as T);
18 |         }
19 |       });
20 | 
21 |       resizeObserver.observe(ref.current);
22 |       return () => resizeObserver.disconnect();
23 |     }
24 |   }, [ref, callback]);
25 | }
26 | 


--------------------------------------------------------------------------------
/src/components/label-input.tsx:
--------------------------------------------------------------------------------
 1 | import { Input, InputProps } from "./orbit/input";
 2 | import { Label } from "./orbit/label";
 3 | 
 4 | export default function LabelInput(
 5 |   props: InputProps & { label: string; requiredDescription?: string }
 6 | ) {
 7 |   return (
 8 |     <div className="flex flex-col gap-2">
 9 |       <Label
10 |         title={props.label}
11 |         required={props.required}
12 |         requiredDescription="required"
13 |       >
14 |         <Input {...props} />
15 |       </Label>
16 |     </div>
17 |   );
18 | }
19 | 


--------------------------------------------------------------------------------
/src/components/mdx/docs.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | import { PropsWithChildren } from "react";
 3 | import { DocNavigation } from "./docs-navigation";
 4 | 
 5 | interface DocTableContentGroup {
 6 |   title: string;
 7 |   href?: string;
 8 |   sub?: DocTableContentGroup[];
 9 | }
10 | 
11 | export type DocTableContent = DocTableContentGroup[];
12 | 
13 | export function DocContent({
14 |   children,
15 |   title,
16 |   group,
17 | }: PropsWithChildren<{ title?: string; group?: string }>) {
18 |   return (
19 |     <>
20 |       {title && (
21 |         <div className="-mx-4 mb-4 border-b px-4 pb-4">
22 |           <div className="max-w-[800px]">
23 |             {group && <span className="text-sm">{group}</span>}
24 |             <h1 className="text-2xl font-semibold">{title}</h1>
25 |           </div>
26 |         </div>
27 |       )}
28 |       <article className={cn("mdx-content max-w-[800px]", { "mt-4": !title })}>
29 |         {children}
30 |       </article>
31 |     </>
32 |   );
33 | }
34 | 
35 | export function DocLayout({
36 |   children,
37 |   content,
38 |   title,
39 | }: PropsWithChildren<{ content: DocTableContent; title?: string }>) {
40 |   return (
41 |     <>
42 |       <DocNavigation content={content} title={title} />
43 |       <div className="md:pl-[300px]">
44 |         <article className="mdx-content p-4">{children}</article>
45 |       </div>
46 |     </>
47 |   );
48 | }
49 | 


--------------------------------------------------------------------------------
/src/components/orbit/banner/index.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | import Image from "next/image";
 3 | 
 4 | type BannerProps = {
 5 |   children?: React.ReactNode;
 6 |   className: string;
 7 |   filter: React.ReactNode;
 8 |   image: string;
 9 |   onClick?: () => void;
10 | };
11 | 
12 | const Banner = ({
13 |   children,
14 |   className,
15 |   filter,
16 |   image,
17 |   onClick,
18 | }: BannerProps) => {
19 |   return (
20 |     <div
21 |       onClick={onClick}
22 |       className="group relative aspect-video w-full cursor-pointer overflow-hidden rounded-lg bg-neutral-800 p-6 shadow-xl after:absolute after:top-0 after:left-0 after:size-full after:rounded-lg after:border-4 after:border-white/20"
23 |     >
24 |       <div className="dither absolute top-0 left-0 z-10 size-full" />
25 | 
26 |       {children}
27 | 
28 |       <div className="absolute top-0 left-0 flex size-full scale-105 items-center">
29 |         {filter}
30 | 
31 |         <Image
32 |           src={image}
33 |           width={600}
34 |           height={600}
35 |           className={cn("scale-110", className)}
36 |           alt="img"
37 |         />
38 |       </div>
39 |     </div>
40 |   );
41 | };
42 | 
43 | export default Banner;
44 | 


--------------------------------------------------------------------------------
/src/components/orbit/banner/pixel-filter.tsx:
--------------------------------------------------------------------------------
 1 | export const PixelFilter = () => {
 2 |   return (
 3 |     <svg width="0" height="0" className="absolute top-0 left-0">
 4 |       <defs>
 5 |         <filter id="pixelate" x="0%" y="0%" width="100%" height="100%">
 6 |           <feGaussianBlur
 7 |             stdDeviation="0"
 8 |             in="SourceGraphic"
 9 |             result="smoothed"
10 |           />
11 |           <feImage
12 |             width="10"
13 |             height="10"
14 |             xlinkHref="/pixel-grid.svg"
15 |             result="displacement-map"
16 |           />
17 |           <feTile in="displacement-map" result="pixelate-map" />
18 |           <feDisplacementMap
19 |             in="smoothed"
20 |             in2="pixelate-map"
21 |             xChannelSelector="R"
22 |             yChannelSelector="G"
23 |             scale="5"
24 |             result="pre-final"
25 |           >
26 |             <animate
27 |               attributeName="scale"
28 |               values="2; 0; 2"
29 |               dur="1s"
30 |               repeatCount="indefinite"
31 |             />
32 |           </feDisplacementMap>
33 |           <feComposite operator="in" in2="SourceGraphic" />
34 |         </filter>
35 |       </defs>
36 |     </svg>
37 |   )
38 | }
39 | 


--------------------------------------------------------------------------------
/src/components/orbit/banner/ripple-filter.tsx:
--------------------------------------------------------------------------------
 1 | const RippleFilter = () => {
 2 |   return (
 3 |     <svg width="0" height="0" className="absolute top-0 left-0">
 4 |       <filter id="ripple" x="0" y="0" width="100%" height="100%">
 5 |         <feTurbulence
 6 |           type="fractalNoise"
 7 |           baseFrequency="0.1"
 8 |           numOctaves="1"
 9 |           result="turbulence"
10 |         >
11 |           <animate
12 |             attributeName="baseFrequency"
13 |             values="0.08; 0.1; 0.08"
14 |             dur="4s"
15 |             repeatCount="indefinite"
16 |           />
17 |         </feTurbulence>
18 | 
19 |         <feDisplacementMap in="SourceGraphic" in2="turbulence" scale="2" />
20 |       </filter>
21 |     </svg>
22 |   )
23 | }
24 | 
25 | export default RippleFilter
26 | 


--------------------------------------------------------------------------------
/src/components/orbit/block.tsx:
--------------------------------------------------------------------------------
 1 | type BlockProps = {
 2 |   children: React.ReactNode
 3 |   title?: string
 4 | }
 5 | 
 6 | const Block = ({ children, title }: BlockProps) => {
 7 |   return (
 8 |     <>
 9 |       {title && (
10 |         <header>
11 |           <h2 className="mt-10 mb-4 text-2xl font-bold tracking-tight">
12 |             {title}
13 |           </h2>
14 |         </header>
15 |       )}
16 |       <div className="flex aspect-video w-full items-center justify-center gap-4 rounded-lg border border-neutral-200 bg-neutral-50 p-10 transition-colors dark:border-neutral-800 dark:bg-neutral-950">
17 |         {children}
18 |       </div>
19 |     </>
20 |   )
21 | }
22 | 
23 | export default Block
24 | 


--------------------------------------------------------------------------------
/src/components/orbit/inset.tsx:
--------------------------------------------------------------------------------
 1 | type InsetProps = {
 2 |   children?: React.ReactNode
 3 | }
 4 | 
 5 | const Inset = ({ children }: InsetProps) => {
 6 |   return (
 7 |     <div className="mx-auto flex w-full max-w-4xl flex-col gap-4 pt-10 pb-40">
 8 |       {children}
 9 |     </div>
10 |   )
11 | }
12 | 
13 | export default Inset
14 | 


--------------------------------------------------------------------------------
/src/components/orbit/label.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | 
 3 | export type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement> & {
 4 |   children?: React.ReactNode;
 5 |   className?: string;
 6 |   isValid?: boolean;
 7 |   title: string;
 8 |   required?: boolean;
 9 |   requiredDescription?: string;
10 | };
11 | 
12 | export const Label = ({
13 |   children,
14 |   className,
15 |   isValid,
16 |   title,
17 |   required,
18 |   requiredDescription,
19 |   ...props
20 | }: LabelProps) => {
21 |   return (
22 |     <label
23 |       className={cn(
24 |         "text-ob-base-200 relative block w-full items-center gap-1 text-sm transition-colors *:w-full",
25 |         className
26 |       )}
27 |       {...props}
28 |     >
29 |       <div className="mb-1 flex w-max">
30 |         {title}
31 |         {required && (
32 |           <>
33 |             <span className="ml-0.5 inline-block">*</span>
34 |             {requiredDescription && !isValid && (
35 |               <span className="text-ob-destructive ml-1 inline-block transition-colors">
36 |                 {requiredDescription}
37 |               </span>
38 |             )}
39 |           </>
40 |         )}
41 |       </div>
42 |       {children}
43 |     </label>
44 |   );
45 | };
46 | 


--------------------------------------------------------------------------------
/src/components/orbit/loader.tsx:
--------------------------------------------------------------------------------
 1 | type LoaderProps = {
 2 |   className?: string;
 3 |   size?: number;
 4 | };
 5 | 
 6 | export const Loader = ({ className, size = 24 }: LoaderProps) => (
 7 |   <svg
 8 |     width="24"
 9 |     height="24"
10 |     viewBox="0 0 24 24"
11 |     xmlns="http://www.w3.org/2000/svg"
12 |     stroke="currentColor"
13 |     className={className}
14 |     style={{ height: size ?? undefined, width: size ?? undefined }}
15 |   >
16 |     <circle
17 |       cx="12"
18 |       cy="12"
19 |       r="9.5"
20 |       fill="none"
21 |       strokeWidth="2"
22 |       strokeLinecap="round"
23 |     >
24 |       <animateTransform
25 |         attributeName="transform"
26 |         type="rotate"
27 |         from="0 12 12"
28 |         to="360 12 12"
29 |         dur="2s"
30 |         repeatCount="indefinite"
31 |       />
32 | 
33 |       <animate
34 |         attributeName="stroke-dasharray"
35 |         values="0 150;42 150;42 150"
36 |         keyTimes="0;0.5;1"
37 |         dur="1.5s"
38 |         repeatCount="indefinite"
39 |       />
40 | 
41 |       <animate
42 |         attributeName="stroke-dashoffset"
43 |         values="0;-16;-59"
44 |         keyTimes="0;0.5;1"
45 |         dur="1.5s"
46 |         repeatCount="indefinite"
47 |       />
48 |     </circle>
49 |     <circle
50 |       cx="12"
51 |       cy="12"
52 |       r="9.5"
53 |       fill="none"
54 |       opacity={0.1}
55 |       strokeWidth="2"
56 |       strokeLinecap="round"
57 |     />
58 |   </svg>
59 | );
60 | 


--------------------------------------------------------------------------------
/src/components/orbit/section.tsx:
--------------------------------------------------------------------------------
 1 | type SectionProps = {
 2 |   children?: React.ReactNode
 3 | }
 4 | 
 5 | const Section = ({ children }: SectionProps) => {
 6 |   return <section className="w-full">{children}</section>
 7 | }
 8 | 
 9 | export default Section
10 | 


--------------------------------------------------------------------------------
/src/components/orbit/toggle.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | 
 3 | type ToggleProps = {
 4 |   size?: "sm" | "base" | "lg";
 5 |   toggled?: boolean;
 6 |   onChange?: (value: boolean) => void;
 7 | };
 8 | 
 9 | export const Toggle = ({ onChange, size = "base", toggled }: ToggleProps) => {
10 |   return (
11 |     <button
12 |       className={cn(
13 |         "ob-focus interactive dark:bg-neutral-750 bg-neutral-250 cursor-pointer rounded-full border border-transparent p-1 transition-colors hover:bg-neutral-300 dark:hover:bg-neutral-700",
14 |         {
15 |           "h-5.5 w-8.5": size === "sm",
16 |           "h-6.5 w-10.5": size === "base",
17 |           "h-7.5 w-12.5": size === "lg",
18 |           "dark:hover:bg-neutral-450 bg-neutral-900 hover:bg-neutral-700 dark:bg-neutral-500":
19 |             toggled,
20 |         }
21 |       )}
22 |       onClick={() => {
23 |         if (onChange) onChange(!toggled);
24 |       }}
25 |     >
26 |       <div
27 |         className={cn(
28 |           "aspect-square h-full rounded-full bg-white transition-all",
29 |           {
30 |             "translate-x-full": toggled,
31 |           }
32 |         )}
33 |       />
34 |     </button>
35 |   );
36 | };
37 | 


--------------------------------------------------------------------------------
/src/components/page-loading.tsx:
--------------------------------------------------------------------------------
 1 | import { PropsWithChildren } from "react";
 2 | import ServerLoadingAnimation from "./icons/server-loading";
 3 | 
 4 | export default function PageLoading({ children }: PropsWithChildren) {
 5 |   return (
 6 |     <div className="flex h-screen w-screen flex-col items-center justify-center gap-4">
 7 |       <ServerLoadingAnimation />
 8 |       {children}
 9 |     </div>
10 |   );
11 | }
12 | 


--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
 5 | import { CheckIcon } from "@radix-ui/react-icons";
 6 | 
 7 | import { cn } from "@/lib/utils";
 8 | 
 9 | const Checkbox = React.forwardRef<
10 |   React.ElementRef<typeof CheckboxPrimitive.Root>,
11 |   React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12 | >(({ className, ...props }, ref) => (
13 |   <CheckboxPrimitive.Root
14 |     ref={ref}
15 |     className={cn(
16 |       "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow-sm focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
17 |       className
18 |     )}
19 |     {...props}
20 |   >
21 |     <CheckboxPrimitive.Indicator
22 |       className={cn("flex items-center justify-center text-current")}
23 |     >
24 |       <CheckIcon className="h-4 w-4" />
25 |     </CheckboxPrimitive.Indicator>
26 |   </CheckboxPrimitive.Root>
27 | ));
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
29 | 
30 | export { Checkbox };
31 | 


--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
 4 | 
 5 | const Collapsible = CollapsiblePrimitive.Root;
 6 | 
 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
 8 | 
 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
10 | 
11 | export { Collapsible, CollapsibleContent, CollapsibleTrigger };
12 | 


--------------------------------------------------------------------------------
/src/components/ui/highlight-text.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | interface Props {
 3 |   text: string;
 4 |   highlight?: string;
 5 | }
 6 | export default function HighlightText({ text, highlight }: Props) {
 7 |   if (!highlight) return <span>{text}</span>;
 8 | 
 9 |   const regex = new RegExp(
10 |     "(" + (highlight ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\
amp;") + ")",
11 |     "i"
12 |   );
13 | 
14 |   const splitedText = text.split(regex);
15 | 
16 |   return (
17 |     <span>
18 |       {splitedText.map((text, idx) => {
19 |         return text.toLowerCase() === (highlight ?? "").toLowerCase() ? (
20 |           <span key={idx} className="bg-yellow-300 text-black">
21 |             {text}
22 |           </span>
23 |         ) : (
24 |           <span key={idx}>{text}</span>
25 |         );
26 |       })}
27 |     </span>
28 |   );
29 | }
30 | 


--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | const HoverCard = HoverCardPrimitive.Root;
 9 | 
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger;
11 | 
12 | const HoverCardContent = React.forwardRef<
13 |   React.ElementRef<typeof HoverCardPrimitive.Content>,
14 |   React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |   <HoverCardPrimitive.Content
17 |     ref={ref}
18 |     align={align}
19 |     sideOffset={sideOffset}
20 |     className={cn(
21 |       "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
22 |       className
23 |     )}
24 |     {...props}
25 |   />
26 | ));
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
28 | 
29 | export { HoverCard, HoverCardTrigger, HoverCardContent };
30 | 


--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
 1 | import * as React from "react";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | export interface InputProps
 6 |   extends React.InputHTMLAttributes<HTMLInputElement> {}
 7 | 
 8 | /**
 9 |  * @deprecated Use `Input` from `@orbit/components` instead.
10 |  */
11 | const Input = React.forwardRef<HTMLInputElement, InputProps>(
12 |   ({ className, type, ...props }, ref) => {
13 |     return (
14 |       <input
15 |         type={type}
16 |         className={cn(
17 |           "border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
18 |           className
19 |         )}
20 |         ref={ref}
21 |         {...props}
22 |       />
23 |     );
24 |   }
25 | );
26 | Input.displayName = "Input";
27 | 
28 | export { Input };
29 | 


--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as LabelPrimitive from "@radix-ui/react-label";
 5 | import { cva, type VariantProps } from "class-variance-authority";
 6 | 
 7 | import { cn } from "@/lib/utils";
 8 | 
 9 | const labelVariants = cva(
10 |   "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | );
12 | 
13 | const Label = React.forwardRef<
14 |   React.ElementRef<typeof LabelPrimitive.Root>,
15 |   React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16 |     VariantProps<typeof labelVariants>
17 | >(({ className, ...props }, ref) => (
18 |   <LabelPrimitive.Root
19 |     ref={ref}
20 |     className={cn(labelVariants(), className)}
21 |     {...props}
22 |   />
23 | ));
24 | Label.displayName = LabelPrimitive.Root.displayName;
25 | 
26 | export { Label };
27 | 


--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as PopoverPrimitive from "@radix-ui/react-popover";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | const Popover = PopoverPrimitive.Root;
 9 | 
10 | const PopoverTrigger = PopoverPrimitive.Trigger;
11 | 
12 | const PopoverAnchor = PopoverPrimitive.Anchor;
13 | 
14 | const PopoverContent = React.forwardRef<
15 |   React.ElementRef<typeof PopoverPrimitive.Content>,
16 |   React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
18 |   <PopoverPrimitive.Portal>
19 |     <PopoverPrimitive.Content
20 |       ref={ref}
21 |       align={align}
22 |       sideOffset={sideOffset}
23 |       className={cn(
24 |         "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
25 |         className
26 |       )}
27 |       {...props}
28 |     />
29 |   </PopoverPrimitive.Portal>
30 | ));
31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
32 | 
33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
34 | 


--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | const Separator = React.forwardRef<
 9 |   React.ElementRef<typeof SeparatorPrimitive.Root>,
10 |   React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11 | >(
12 |   (
13 |     { className, orientation = "horizontal", decorative = true, ...props },
14 |     ref
15 |   ) => (
16 |     <SeparatorPrimitive.Root
17 |       ref={ref}
18 |       decorative={decorative}
19 |       orientation={orientation}
20 |       className={cn(
21 |         "shrink-0 bg-border",
22 |         orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
23 |         className
24 |       )}
25 |       {...props}
26 |     />
27 |   )
28 | );
29 | Separator.displayName = SeparatorPrimitive.Root.displayName;
30 | 
31 | export { Separator };
32 | 


--------------------------------------------------------------------------------
/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { useTheme } from "next-themes";
 4 | import { Toaster as Sonner } from "sonner";
 5 | 
 6 | type ToasterProps = React.ComponentProps<typeof Sonner>;
 7 | 
 8 | const Toaster = ({ ...props }: ToasterProps) => {
 9 |   const { resolvedTheme } = useTheme();
10 | 
11 |   return (
12 |     <Sonner
13 |       theme={resolvedTheme as ToasterProps["theme"]}
14 |       className="toaster group"
15 |       toastOptions={{
16 |         classNames: {
17 |           toast:
18 |             "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
19 |           description: "group-[.toast]:text-muted-foreground",
20 |           actionButton:
21 |             "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
22 |           cancelButton:
23 |             "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
24 |           success: "*:data-icon:text-green-500",
25 |           error: "*:data-icon:text-red-500",
26 |         },
27 |       }}
28 |       {...props}
29 |     />
30 |   );
31 | };
32 | 
33 | export { Toaster };
34 | 


--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
 1 | import * as React from "react";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | export interface TextareaProps
 6 |   extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
 7 | 
 8 | const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
 9 |   ({ className, ...props }, ref) => {
10 |     return (
11 |       <textarea
12 |         className={cn(
13 |           "border-input placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[60px] w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
14 |           className
15 |         )}
16 |         ref={ref}
17 |         {...props}
18 |       />
19 |     );
20 |   }
21 | );
22 | Textarea.displayName = "Textarea";
23 | 
24 | export { Textarea };
25 | 


--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
 4 | import * as React from "react";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | interface TooltipProviderProps {
 9 |   children: React.ReactNode;
10 |   delayDuration?: number;
11 |   skipDelayDuration?: number;
12 |   disableHoverableContent?: boolean;
13 | }
14 | 
15 | const TooltipProvider =
16 |   TooltipPrimitive.Provider as React.FC<TooltipProviderProps>;
17 | 
18 | const Tooltip = TooltipPrimitive.Root;
19 | 
20 | const TooltipTrigger = TooltipPrimitive.Trigger;
21 | 
22 | const TooltipContent = React.forwardRef<
23 |   React.ElementRef<typeof TooltipPrimitive.Content>,
24 |   React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
25 | >(({ className, sideOffset = 4, ...props }, ref) => (
26 |   <TooltipPrimitive.Content
27 |     ref={ref}
28 |     sideOffset={sideOffset}
29 |     className={cn(
30 |       "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md bg-white px-3 py-1.5 text-sm text-black shadow-sm dark:bg-neutral-800 dark:text-neutral-200",
31 |       className
32 |     )}
33 |     {...props}
34 |   />
35 | ));
36 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
37 | 
38 | export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
39 | 


--------------------------------------------------------------------------------
/src/const.ts:
--------------------------------------------------------------------------------
 1 | export enum MessageChannelName {
 2 |   OPEN_NEW_TAB = "OPEN_NEW_TAB",
 3 |   OPEN_CONTEXT_MENU = "OPEN_CONTEXT_MENU",
 4 |   CLOSE_TABS = "CLOSE_TAB",
 5 | }
 6 | 
 7 | export const TAB_PREFIX_SAVED_QUERY = "saved-query-";
 8 | export const WEBSITE_NAME = "Outerbase Studio";
 9 | export const WEBSITE_GENERAL_DESCRIPTION = `${WEBSITE_NAME} is a fully-featured, lightweight GUI client for managing SQLite-based databases like Turso, LibSQL, and rqlite. It runs entirely in your browser, so there's no need to download anything`;
10 | 


--------------------------------------------------------------------------------
/src/constants/http-status.ts:
--------------------------------------------------------------------------------
 1 | export enum HttpStatus {
 2 |   // 200
 3 |   OK = 200,
 4 |   CREATED = 201,
 5 |   ACCEPTED = 202,
 6 |   NO_CONTENT = 204,
 7 | 
 8 |   // 300
 9 |   MOVED_PERMANENTLY = 301,
10 |   FOUND = 302,
11 |   TEMPORARY_REDIRECT = 307,
12 |   PERMANENT_REDIRECT = 308,
13 | 
14 |   // 400
15 |   BAD_REQUEST = 400,
16 |   UNAUTHORIZED = 401,
17 |   PAYMENT_REQUIRED = 402,
18 |   FORBIDDEN = 403,
19 |   NOT_FOUND = 404,
20 |   METHOD_NOT_ALLOWED = 405,
21 |   CONTENT_TOO_LARGE = 413,
22 |   URI_TOO_LONG = 414,
23 |   IM_A_TEAPOT = 418,
24 |   TOO_MANY_REQUESTS = 429,
25 | 
26 |   // 500
27 |   INTERNAL_SERVER_ERROR = 500,
28 |   NOT_IMPLEMENTED = 501,
29 |   BAD_GATEWAY = 502,
30 |   SERVICE_UNAVAILABLE = 503,
31 |   INSUFFICIENT_STORAGE = 507,
32 | }
33 | 


--------------------------------------------------------------------------------
/src/context/driver-provider.tsx:
--------------------------------------------------------------------------------
 1 | import { StudioExtensionManager } from "@/core/extension-manager";
 2 | import AgentDriverList from "@/drivers/agent/list";
 3 | import type { BaseDriver } from "@/drivers/base-driver";
 4 | import { SavedDocDriver } from "@/drivers/saved-doc/saved-doc-driver";
 5 | import { noop } from "lodash";
 6 | import { type PropsWithChildren, createContext, useContext } from "react";
 7 | 
 8 | export interface StudioContextProps {
 9 |   databaseDriver: BaseDriver;
10 |   docDriver?: SavedDocDriver;
11 | 
12 |   // Moving from useConfig previously
13 |   color: string;
14 |   name: string;
15 |   onBack?: () => void;
16 |   extensions: StudioExtensionManager;
17 |   containerClassName?: string;
18 |   agentDriver?: AgentDriverList;
19 | }
20 | 
21 | const StudioContext = createContext<StudioContextProps>({
22 |   databaseDriver: {} as unknown as BaseDriver,
23 |   name: "",
24 |   color: "",
25 |   extensions: new StudioExtensionManager([]),
26 |   onBack: noop,
27 |   containerClassName: "",
28 | });
29 | 
30 | export function useStudioContext() {
31 |   return useContext(StudioContext);
32 | }
33 | 
34 | export function StudioContextProvider({
35 |   children,
36 |   value,
37 | }: PropsWithChildren<{ value: StudioContextProps }>) {
38 |   return (
39 |     <StudioContext.Provider value={value}>{children}</StudioContext.Provider>
40 |   );
41 | }
42 | 


--------------------------------------------------------------------------------
/src/core/builtin-tab/open-erd-tab.tsx:
--------------------------------------------------------------------------------
 1 | import RelationalDiagramTab from "@/components/gui/tabs/relational-diagram-tab";
 2 | import { TreeStructure } from "@phosphor-icons/react";
 3 | import { createTabExtension } from "../extension-tab";
 4 | 
 5 | export const builtinOpenERDTab = createTabExtension({
 6 |   name: "erd",
 7 |   key: () => "",
 8 |   generate: () => ({
 9 |     title: "Relational Diagram",
10 |     component: <RelationalDiagramTab />,
11 |     icon: TreeStructure,
12 |   }),
13 | });
14 | 


--------------------------------------------------------------------------------
/src/core/builtin-tab/open-mass-drop-table.tsx:
--------------------------------------------------------------------------------
 1 | import MassDropTableTab from "@/components/gui/tabs/mass-drop-table";
 2 | import { StackMinus } from "@phosphor-icons/react";
 3 | import { createTabExtension } from "../extension-tab";
 4 | 
 5 | export const builtinMassDropTableTab = createTabExtension({
 6 |   name: "mass-drop-table",
 7 |   key: () => "",
 8 |   generate: () => ({
 9 |     title: "Mass Drop Tables",
10 |     component: <MassDropTableTab />,
11 |     icon: StackMinus,
12 |   }),
13 | });
14 | 


--------------------------------------------------------------------------------
/src/core/builtin-tab/open-query-tab.tsx:
--------------------------------------------------------------------------------
 1 | import QueryWindow from "@/components/gui/tabs/query-tab";
 2 | import { generateId } from "@/lib/generate-id";
 3 | import { Binoculars } from "@phosphor-icons/react";
 4 | import { createTabExtension } from "../extension-tab";
 5 | 
 6 | let QUERY_COUNTER = 2;
 7 | 
 8 | export const builtinOpenQueryTab = createTabExtension<
 9 |   | {
10 |       name?: string;
11 |       saved?: {
12 |         namespaceName?: string;
13 |         key: string;
14 |         sql: string;
15 |       };
16 |     }
17 |   | undefined
18 | >({
19 |   name: "query",
20 |   key: (options) => {
21 |     if (options?.saved) {
22 |       return options.saved.key;
23 |     }
24 |     return generateId();
25 |   },
26 |   generate: (options) => {
27 |     const title = options?.saved
28 |       ? (options.name ?? "Query")
29 |       : "Query " + (QUERY_COUNTER++).toString();
30 | 
31 |     const component = options?.saved ? (
32 |       <QueryWindow
33 |         initialName={title}
34 |         initialCode={options.saved.sql}
35 |         initialSavedKey={options.saved.key}
36 |         initialNamespace={options.saved.namespaceName}
37 |       />
38 |     ) : (
39 |       <QueryWindow initialName={title} />
40 |     );
41 | 
42 |     return {
43 |       title: options?.name ?? "Query",
44 |       component,
45 |       icon: Binoculars,
46 |     };
47 |   },
48 | });
49 | 


--------------------------------------------------------------------------------
/src/core/builtin-tab/open-schema-tab.tsx:
--------------------------------------------------------------------------------
 1 | import SchemaEditorTab from "@/components/gui/tabs/schema-editor-tab";
 2 | import { LucideTableProperties } from "lucide-react";
 3 | import { createTabExtension } from "../extension-tab";
 4 | 
 5 | export const builtinOpenSchemaTab = createTabExtension<
 6 |   | {
 7 |       schemaName?: string;
 8 |       tableName?: string;
 9 |     }
10 |   | undefined
11 | >({
12 |   name: "schema",
13 |   key: (options) => {
14 |     if (!options?.tableName) {
15 |       return "create";
16 |     }
17 |     return `${options.schemaName}-${options.tableName}`;
18 |   },
19 |   generate: (options) => ({
20 |     title: options?.tableName ? options.tableName : "New Table",
21 |     component: (
22 |       <SchemaEditorTab
23 |         tableName={options?.tableName}
24 |         schemaName={options?.schemaName}
25 |       />
26 |     ),
27 |     icon: LucideTableProperties,
28 |   }),
29 | });
30 | 


--------------------------------------------------------------------------------
/src/core/builtin-tab/open-table-tab.tsx:
--------------------------------------------------------------------------------
 1 | import TableDataWindow from "@/components/gui/tabs/table-data-tab";
 2 | import { Table } from "@phosphor-icons/react";
 3 | import { createTabExtension } from "../extension-tab";
 4 | 
 5 | export const builtinOpenTableTab = createTabExtension<{
 6 |   schemaName: string;
 7 |   tableName: string;
 8 | }>({
 9 |   name: "table",
10 |   key: (options) => {
11 |     return options.schemaName + "-" + options?.tableName;
12 |   },
13 |   generate: (options) => ({
14 |     title: options.tableName,
15 |     component: (
16 |       <TableDataWindow
17 |         tableName={options.tableName}
18 |         schemaName={options.schemaName}
19 |       />
20 |     ),
21 |     icon: Table,
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/core/channel-builtin.tsx:
--------------------------------------------------------------------------------
 1 | import { LucideIcon } from "lucide-react";
 2 | import { CommunicationChannel } from "./channel";
 3 | 
 4 | export interface StudioContextMenuItem {
 5 |   type?: "check";
 6 |   checked?: boolean;
 7 |   title?: string | JSX.Element;
 8 |   shortcut?: string;
 9 |   separator?: boolean;
10 |   disabled?: boolean;
11 |   destructive?: boolean;
12 |   onClick?: () => void;
13 |   sub?: OpenContextMenuList;
14 |   subWidth?: number;
15 |   icon?: LucideIcon;
16 | }
17 | 
18 | export type OpenContextMenuList = Array<StudioContextMenuItem>;
19 | 
20 | export interface OpenContextMenuOptions {
21 |   contextMenu: OpenContextMenuList;
22 |   x: number;
23 |   y: number;
24 | }
25 | 
26 | export const contextMenuChannel =
27 |   new CommunicationChannel<OpenContextMenuOptions>();
28 | 
29 | export function openContextMenu(options: OpenContextMenuOptions) {
30 |   contextMenuChannel.send(options);
31 | }
32 | 
33 | export function openContextMenuFromEvent(contextMenu: OpenContextMenuList) {
34 |   return function handleMouseEvent(e: React.MouseEvent) {
35 |     openContextMenu({
36 |       contextMenu,
37 |       x: e.pageX,
38 |       y: e.pageY,
39 |     });
40 | 
41 |     e.preventDefault();
42 |     return false;
43 |   };
44 | }
45 | 


--------------------------------------------------------------------------------
/src/core/channel.tsx:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Communication channel for sending and receiving data between components.
 3 |  */
 4 | 
 5 | type CommunicationChannelReceiver<T = unknown, P = void> = (data: T) => P;
 6 | 
 7 | export class CommunicationChannel<T = unknown, P = void> {
 8 |   private listeners: CommunicationChannelReceiver<T, P>[] = [];
 9 | 
10 |   send(data: T): P | undefined {
11 |     return this.listeners.map((listener) => listener(data)).pop();
12 |   }
13 | 
14 |   listen(receiver: CommunicationChannelReceiver<T, P>) {
15 |     this.listeners.push(receiver);
16 |     return () => {
17 |       this.listeners = this.listeners.filter(
18 |         (listener) => listener !== receiver
19 |       );
20 |     };
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/src/core/command/index.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * scc stands for Studio Core Command. It is a collection of commands that are
 3 |  * available to Outerbase Studio.
 4 |  */
 5 | 
 6 | import { builtinOpenERDTab } from "../builtin-tab/open-erd-tab";
 7 | import { builtinMassDropTableTab } from "../builtin-tab/open-mass-drop-table";
 8 | import { builtinOpenQueryTab } from "../builtin-tab/open-query-tab";
 9 | import { builtinOpenSchemaTab } from "../builtin-tab/open-schema-tab";
10 | import { builtinOpenTableTab } from "../builtin-tab/open-table-tab";
11 | import { tabCloseChannel } from "../extension-tab";
12 | 
13 | export const scc = {
14 |   tabs: {
15 |     openBuiltinQuery: builtinOpenQueryTab.open,
16 |     openBuiltinTable: builtinOpenTableTab.open,
17 |     openBuiltinSchema: builtinOpenSchemaTab.open,
18 |     openBuiltinERD: builtinOpenERDTab.open,
19 |     openBuiltinMassDropTable: builtinMassDropTableTab.open,
20 | 
21 |     close: (keys: string[]) => {
22 |       tabCloseChannel.send(keys);
23 |     },
24 |   },
25 | };
26 | 


--------------------------------------------------------------------------------
/src/core/extension-base.tsx:
--------------------------------------------------------------------------------
 1 | import { StudioExtensionContext } from "./extension-manager";
 2 | 
 3 | export abstract class IStudioExtension {
 4 |   abstract extensionName: string;
 5 |   abstract init(studio: StudioExtensionContext): void;
 6 |   abstract cleanup(): void;
 7 | }
 8 | 
 9 | export abstract class StudioExtension extends IStudioExtension {
10 |   cleanup() {
11 |     // Do nothing by default
12 |   }
13 | }
14 | 


--------------------------------------------------------------------------------
/src/core/query-pipeline.tsx:
--------------------------------------------------------------------------------
 1 | export class BeforeQueryPipeline {
 2 |   metadata: Record<string, string> = {};
 3 | 
 4 |   constructor(
 5 |     protected type: "query" | "transaction" | "batch",
 6 |     protected statements: string[]
 7 |   ) {}
 8 | 
 9 |   getMetadata(name: string) {
10 |     return this.metadata[name];
11 |   }
12 | 
13 |   getMetadataList() {
14 |     return structuredClone(this.metadata);
15 |   }
16 | 
17 |   setMetadata(name: string, value: string) {
18 |     this.metadata[name] = value;
19 |   }
20 | 
21 |   getStatments() {
22 |     return this.statements;
23 |   }
24 | 
25 |   updateStatements(statements: string[]) {
26 |     this.statements = statements;
27 |   }
28 | 
29 |   getType() {
30 |     return this.type;
31 |   }
32 | }
33 | 


--------------------------------------------------------------------------------
/src/core/standard-extension.tsx:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * This contains the standard extensions as a base for all databases.
 3 |  */
 4 | 
 5 | import ColumnDescriptorExtension from "@/extensions/column-descriptor";
 6 | import DataDecoratorExtension from "@/extensions/data-decorator";
 7 | import QueryHistoryConsoleLogExtension from "@/extensions/query-console-log";
 8 | import TriggerEditorExtension from "@/extensions/trigger-editor";
 9 | import ViewEditorExtension from "@/extensions/view-editor";
10 | 
11 | export function createStandardExtensions() {
12 |   return [
13 |     new QueryHistoryConsoleLogExtension(),
14 |     new ViewEditorExtension(),
15 |     new ColumnDescriptorExtension(),
16 |     new DataDecoratorExtension(),
17 |   ];
18 | }
19 | 
20 | export function createSQLiteExtensions() {
21 |   return [...createStandardExtensions(), new TriggerEditorExtension()];
22 | }
23 | 
24 | export function createMySQLExtensions() {
25 |   return [...createStandardExtensions(), new TriggerEditorExtension()];
26 | }
27 | 
28 | export function createPostgreSQLExtensions() {
29 |   return createStandardExtensions();
30 | }
31 | 


--------------------------------------------------------------------------------
/src/drivers/agent/base.ts:
--------------------------------------------------------------------------------
 1 | import { DatabaseSchemas } from "../base-driver";
 2 | 
 3 | export interface AgentPromptOption {
 4 |   schema?: DatabaseSchemas;
 5 |   selectedSchema?: string;
 6 |   selected: string;
 7 | }
 8 | 
 9 | export interface AgentPromptResponse {
10 |   result: string;
11 |   id: string;
12 | }
13 | 
14 | export abstract class AgentBaseDriver {
15 |   /**
16 |    *
17 |    * @param message User message
18 |    * @param previousId Previous message id. If not provided, it is a new conversation
19 |    * @param option
20 |    */
21 |   abstract run(
22 |     message: string,
23 |     previousId: string | undefined,
24 |     option: AgentPromptOption
25 |   ): Promise<string>;
26 | }
27 | 


--------------------------------------------------------------------------------
/src/drivers/agent/chatgpt.ts:
--------------------------------------------------------------------------------
 1 | import { BaseDriver } from "../base-driver";
 2 | import CommonAgentDriverImplementation, { CommonAgentMessage } from "./common";
 3 | interface ChatGPTResponse {
 4 |   choices: { message: { role: string; content: string } }[];
 5 | }
 6 | 
 7 | export class ChatGPTDriver extends CommonAgentDriverImplementation {
 8 |   constructor(
 9 |     protected driver: BaseDriver,
10 |     protected token: string
11 |   ) {
12 |     super(driver);
13 |   }
14 | 
15 |   async query(messages: CommonAgentMessage[]): Promise<string> {
16 |     const response = await fetch("https://api.openai.com/v1/chat/completions", {
17 |       method: "POST",
18 |       headers: {
19 |         Authorization: `Bearer ${this.token}`,
20 |         "Content-Type": "application/json",
21 |       },
22 |       body: JSON.stringify({
23 |         model: "gpt-4o-mini",
24 |         temperature: 0,
25 |         messages: messages,
26 |       }),
27 |     });
28 | 
29 |     const jsonResponse = (await response.json()) as ChatGPTResponse;
30 |     return jsonResponse.choices[0].message.content;
31 |   }
32 | }
33 | 


--------------------------------------------------------------------------------
/src/drivers/agent/cloudflare.ts:
--------------------------------------------------------------------------------
 1 | import { format } from "sql-formatter";
 2 | import { BaseDriver } from "../base-driver";
 3 | import CommonAgentDriverImplementation, { CommonAgentMessage } from "./common";
 4 | 
 5 | export default class CloudflareAgentDriver extends CommonAgentDriverImplementation {
 6 |   constructor(
 7 |     protected driver: BaseDriver,
 8 |     protected model:
 9 |       | "@cf/defog/sqlcoder-7b-2"
10 |       | "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
11 |   ) {
12 |     super(driver);
13 |   }
14 | 
15 |   async query(messages: CommonAgentMessage[]): Promise<string> {
16 |     const response = await fetch(
17 |       "https://studio-ai-agent.outerbase.workers.dev/chat/complete",
18 |       {
19 |         method: "POST",
20 |         headers: {
21 |           "Content-Type": "application/json",
22 |         },
23 |         body: JSON.stringify({
24 |           model: this.model,
25 |           messages: messages,
26 |         }),
27 |       }
28 |     );
29 | 
30 |     const jsonResponse = (await response.json()) as { response: string };
31 |     return jsonResponse.response;
32 |   }
33 | 
34 |   processResult(result: string): string {
35 |     if (this.model === "@cf/defog/sqlcoder-7b-2") {
36 |       return format(result, {
37 |         language: "sqlite",
38 |       });
39 |     }
40 | 
41 |     return super.processResult(result);
42 |   }
43 | }
44 | 


--------------------------------------------------------------------------------
/src/drivers/board-source/README:
--------------------------------------------------------------------------------
1 | This is multiple database driver that will be used for board


--------------------------------------------------------------------------------
/src/drivers/board-source/base-source.ts:
--------------------------------------------------------------------------------
 1 | import { BaseDriver, DatabaseResultSet, DatabaseSchemas } from "../base-driver";
 2 | 
 3 | export interface BoardSource {
 4 |   id: string;
 5 |   name: string;
 6 |   type: string;
 7 | }
 8 | 
 9 | export abstract class BoardSourceDriver {
10 |   abstract sourceList(): BoardSource[];
11 |   abstract getDriver(sourceId: string): BaseDriver;
12 |   abstract query(
13 |     sourceId: string,
14 |     statement: string
15 |   ): Promise<DatabaseResultSet>;
16 |   abstract schemas(
17 |     sourceId: string
18 |   ): Promise<{ schema: DatabaseSchemas; selectedSchema?: string }>;
19 |   abstract cleanup(): void;
20 | }
21 | 


--------------------------------------------------------------------------------
/src/drivers/board-storage/base.ts:
--------------------------------------------------------------------------------
 1 | import { DashboardProps } from "@/components/board";
 2 | import { ChartValue } from "@/components/chart/chart-type";
 3 | 
 4 | export abstract class IBoardStorageDriver {
 5 |   abstract save(value: DashboardProps): Promise<unknown>;
 6 |   abstract remove(chartId: string): Promise<unknown>;
 7 |   abstract add(chart: ChartValue): Promise<ChartValue | undefined>;
 8 |   abstract update(
 9 |     chartId: string,
10 |     chart: ChartValue
11 |   ): Promise<ChartValue | undefined>;
12 | }
13 | 


--------------------------------------------------------------------------------
/src/drivers/database/cloudflare-d1.ts:
--------------------------------------------------------------------------------
 1 | import { transformCloudflareD1 } from "@outerbase/sdk-transform";
 2 | import { DatabaseResultSet, QueryableBaseDriver } from "../base-driver";
 3 | 
 4 | interface CloudflareResult {
 5 |   results: {
 6 |     columns: string[];
 7 |     rows: unknown[][];
 8 |   };
 9 |   meta: {
10 |     duration: number;
11 |     changes: number;
12 |     last_row_id: number;
13 |     rows_read: number;
14 |     rows_written: number;
15 |   };
16 | }
17 | 
18 | interface CloudflareResponse {
19 |   error: string;
20 |   result: CloudflareResult[];
21 | }
22 | 
23 | export class CloudflareD1Queryable implements QueryableBaseDriver {
24 |   constructor(
25 |     protected url: string,
26 |     protected headers: Record<string, string>
27 |   ) {
28 |     this.headers = headers;
29 |     this.url = url;
30 |   }
31 | 
32 |   async transaction(stmts: string[]): Promise<DatabaseResultSet[]> {
33 |     const r = await fetch(this.url, {
34 |       method: "POST",
35 |       headers: { ...this.headers, "Content-Type": "application/json" },
36 |       body: JSON.stringify({
37 |         sql: stmts.join(";"),
38 |       }),
39 |     });
40 | 
41 |     const json: CloudflareResponse = await r.json();
42 | 
43 |     if (json.error) throw new Error(json.error);
44 |     return json.result.map(transformCloudflareD1);
45 |   }
46 | 
47 |   async query(stmt: string): Promise<DatabaseResultSet> {
48 |     return (await this.transaction([stmt]))[0];
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/src/drivers/database/valtown.ts:
--------------------------------------------------------------------------------
 1 | import { DatabaseResultSet, QueryableBaseDriver } from "@/drivers/base-driver";
 2 | import { InStatement, ResultSet } from "@libsql/client";
 3 | import { transformRawResult } from "./turso";
 4 | 
 5 | export class ValtownQueryable implements QueryableBaseDriver {
 6 |   constructor(protected token: string) {}
 7 | 
 8 |   async transaction(stmts: InStatement[]): Promise<DatabaseResultSet[]> {
 9 |     const r = await fetch(`https://api.val.town/v1/sqlite/batch`, {
10 |       method: "POST",
11 |       headers: {
12 |         Authorization: "Bearer " + this.token,
13 |         "Content-Type": "application/json",
14 |       },
15 |       body: JSON.stringify({
16 |         statements: stmts,
17 |         mode: "write",
18 |       }),
19 |     });
20 | 
21 |     const json = await r.json();
22 |     return json.map(transformRawResult);
23 |   }
24 | 
25 |   async query(stmt: InStatement): Promise<DatabaseResultSet> {
26 |     const r = await fetch(`https://api.val.town/v1/sqlite/execute`, {
27 |       method: "POST",
28 |       headers: {
29 |         Authorization: "Bearer " + this.token,
30 |         "Content-Type": "application/json",
31 |       },
32 |       body: JSON.stringify({ statement: stmt }),
33 |     });
34 | 
35 |     const json = await r.json();
36 | 
37 |     return transformRawResult(json as ResultSet);
38 |   }
39 | }
40 | 


--------------------------------------------------------------------------------
/src/drivers/helpers.ts:
--------------------------------------------------------------------------------
 1 | import { SavedConnectionRawLocalStorage } from "@/app/(theme)/connect/saved-connection-storage";
 2 | import { CloudflareD1Queryable } from "./database/cloudflare-d1";
 3 | import CloudflareWAEDriver from "./database/cloudflare-wae";
 4 | import { RqliteQueryable } from "./database/rqlite";
 5 | import { StarbaseQuery } from "./database/starbasedb";
 6 | import TursoDriver from "./database/turso";
 7 | import { ValtownQueryable } from "./database/valtown";
 8 | import { SqliteLikeBaseDriver } from "./sqlite-base-driver";
 9 | 
10 | export function createLocalDriver(conn: SavedConnectionRawLocalStorage) {
11 |   if (conn.driver === "rqlite") {
12 |     return new SqliteLikeBaseDriver(
13 |       new RqliteQueryable(conn.url!, conn.username, conn.password)
14 |     );
15 |   } else if (conn.driver === "valtown") {
16 |     return new SqliteLikeBaseDriver(new ValtownQueryable(conn.token!));
17 |   } else if (conn.driver === "cloudflare-d1") {
18 |     return new SqliteLikeBaseDriver(
19 |       new CloudflareD1Queryable("/proxy/d1", {
20 |         Authorization: "Bearer " + conn.token,
21 |         "x-account-id": conn.username ?? "",
22 |         "x-database-id": conn.database ?? "",
23 |       })
24 |     );
25 |   } else if (conn.driver === "starbase") {
26 |     return new SqliteLikeBaseDriver(new StarbaseQuery(conn.url!, conn.token!));
27 |   } else if (conn.driver === "cloudflare-wae") {
28 |     return new CloudflareWAEDriver(conn.username!, conn.token!);
29 |   }
30 | 
31 |   return new TursoDriver(conn.url!, conn.token!, true);
32 | }
33 | 


--------------------------------------------------------------------------------
/src/drivers/sql-helper.ts:
--------------------------------------------------------------------------------
 1 | export type SQLStatementType =
 2 |   | "SELECT"
 3 |   | "INSERT"
 4 |   | "UPDATE"
 5 |   | "CREATE_TABLE"
 6 |   | "ALTER_TABLE"
 7 |   | "DROP_TABLE"
 8 |   | "CREATE_INDEX"
 9 |   | "DROP_INDEX"
10 |   | "CREATE_VIEW"
11 |   | "DROP_VIEW"
12 |   | "CREATE_TRIGGER"
13 |   | "DROP_TRIGGER"
14 |   | "OTHER";
15 | 
16 | export function getSQLStatementType(statement: string): SQLStatementType {
17 |   let trimmed = statement.trim().toUpperCase();
18 | 
19 |   // Reduce continuous whitespaces to single whitespace
20 |   trimmed = trimmed.replace(/\s+/g, " ");
21 | 
22 |   // Replace the "IF NOT EXISTS" clause with an empty string
23 |   trimmed = trimmed.replace("IF NOT EXISTS", "");
24 | 
25 |   if (trimmed.startsWith("SELECT")) return "SELECT";
26 |   if (trimmed.startsWith("INSERT")) return "INSERT";
27 |   if (trimmed.startsWith("UPDATE")) return "UPDATE";
28 |   if (trimmed.startsWith("CREATE TABLE")) return "CREATE_TABLE";
29 |   if (trimmed.startsWith("ALTER TABLE")) return "ALTER_TABLE";
30 |   if (trimmed.startsWith("DROP TABLE")) return "DROP_TABLE";
31 |   if (trimmed.startsWith("CREATE INDEX")) return "CREATE_INDEX";
32 |   if (trimmed.startsWith("DROP INDEX")) return "DROP_INDEX";
33 |   if (trimmed.startsWith("CREATE VIEW")) return "CREATE_VIEW";
34 |   if (trimmed.startsWith("DROP VIEW")) return "DROP_VIEW";
35 |   if (trimmed.startsWith("CREATE TRIGGER")) return "CREATE_TRIGGER";
36 |   if (trimmed.startsWith("DROP TRIGGER")) return "DROP_TRIGGER";
37 | 
38 |   return "OTHER";
39 | }
40 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/abs.md:
--------------------------------------------------------------------------------
 1 | abs(X)
 2 | 
 3 | Returns the absolute value of X. If X is a string or blob, it returns 0.
 4 | 
 5 | ```
 6 | SELECT abs(-5);  --> 5
 7 | SELECT abs("-3");  --> 3
 8 | SELECT abs("libsql");  --> 0
 9 | ```
10 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/avg.md:
--------------------------------------------------------------------------------
1 | avg(X)
2 | 
3 | The avg() function returns the average of all non-NULL values in a group, interpreting non-numeric
4 | strings and BLOBs as 0. It always yields a floating point result if there is at least one non-NULL
5 | input and returns NULL if there are no non-NULL inputs.
6 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/changes.md:
--------------------------------------------------------------------------------
1 | changes()
2 | 
3 | The changes() function returns the number of rows altered by the latest INSERT, DELETE, or UPDATE statement, excluding changes from lower-level triggers.
4 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/char.md:
--------------------------------------------------------------------------------
1 | char(X1,X2,...,XN)
2 | 
3 | The char(X1,X2,...,XN) function returns a string composed of characters having the unicode code point values of integers X1 through XN, respectively.
4 | 
5 | ```
6 | SELECT CHAR(65, 66, 67);
7 | -> 'ABC'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/coalesce.md:
--------------------------------------------------------------------------------
1 | coalesce(X,Y,...)
2 | 
3 | The coalesce() function returns a copy of its first non-NULL argument, or NULL if all arguments are NULL. Coalesce() must have at least 2 arguments.
4 | 
5 | ```
6 | select coalesce(null, 50);
7 | -> 50
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/concat.md:
--------------------------------------------------------------------------------
1 | concat(X,...)
2 | 
3 | The concat function returns a string formed by concatenating all its non-NULL arguments. If all arguments are NULL, it returns an empty string.
4 | 
5 | ```
6 | select concat('hello', ' ', 'world')
7 | -> 'hello world'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/concat_ws.md:
--------------------------------------------------------------------------------
1 | concat_ws(SEP,X,...)
2 | 
3 | The concat_ws concatenates all non-null arguments beyond the first, using the first argument as a separator. If the first argument is NULL, it returns NULL. If all other arguments are NULL, it returns an empty string.
4 | 
5 | ```
6 | select concat_ws(', ', 'hello', 'world')
7 | -> 'hello world'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/count.md:
--------------------------------------------------------------------------------
1 | count(X), count(\*)
2 | 
3 | The count(X) function returns a count of the number of times that X is not NULL in a group.
4 | The count(\*) function (with no arguments) returns the total number of rows in the group.
5 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/format.md:
--------------------------------------------------------------------------------
1 | format(FORMAT,...)
2 | 
3 | The FORMAT() function, similar to C's printf(), uses a format string (first argument) to construct the output with values from subsequent arguments.
4 | 
5 | ```
6 | select format('i am %d years old', 50);
7 | -> 'i am 50 years old'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/fts5.md:
--------------------------------------------------------------------------------
 1 | fts5(...col)
 2 | 
 3 | FTS5 is an SQLite virtual table module that provides full-text search functionality to database applications.
 4 | 
 5 | ```
 6 | CREATE VIRTUAL TABLE movie_fts USING fts5(title, summary);
 7 | CREATE VIRTUAL TABLE name_fts USING fts5(name, tokenize='trigram');
 8 | ```
 9 | 
10 | **External Content Tables**
11 | 
12 | ```
13 | CREATE VIRTUAL TABLE student_fts USING fts5(
14 |     name,
15 |     tokenize='trigram',
16 |     content='student',
17 |     content_rowid='id'
18 | );
19 | ```
20 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/glob.md:
--------------------------------------------------------------------------------
1 | glob(X,Y)
2 | 
3 | The GLOB operator is like LIKE but uses Unix file globbing syntax and is case-sensitive.
4 | 
5 | ```
6 | select glob('*hello*', 'hello world');
7 | -> 1
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/group_concat.md:
--------------------------------------------------------------------------------
1 | group_concat(X, Y)
2 | 
3 | The function concatenates all non-NULL values of X into a string, using Y as the separator. If Y is omitted, a comma (",") is used.
4 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/hex.md:
--------------------------------------------------------------------------------
1 | hex(X)
2 | 
3 | The hex() function converts its BLOB argument into an upper-case hexadecimal string.
4 | 
5 | ```
6 | select hex(x'ffeeaa');
7 | -> FFEEAA
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/ifnull.md:
--------------------------------------------------------------------------------
1 | ifnull(X,Y)
2 | 
3 | The ifnull() function returns the first non-NULL argument, or NULL if both are NULL. It requires exactly 2 arguments and is equivalent to coalesce() with two arguments.
4 | 
5 | ```
6 | select ifnull(null, 5);
7 | -> 5
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/iif.md:
--------------------------------------------------------------------------------
1 | iif(X,Y,Z)
2 | 
3 | The iif(X,Y,Z) function returns the value Y if X is true, and Z otherwise
4 | 
5 | ```
6 | select iif(age >= 18, 'adult', 'underage');
7 | -> 'underage'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/instr.md:
--------------------------------------------------------------------------------
1 | instr(X, Y)
2 | 
3 | The instr(X, Y) function returns the position of the first occurrence of string Y within string X plus 1, or 0 if Y is not found in X.
4 | 
5 | ```
6 | select instr('hello world', 'wo');
7 | -> 7
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/last_insert_rowid.md:
--------------------------------------------------------------------------------
1 | last_insert_rowid()
2 | 
3 | The last_insert_rowid() function returns the ROWID of the last row insert from the database connection which invoked the function.
4 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/length.md:
--------------------------------------------------------------------------------
 1 | length(X)
 2 | 
 3 | The length(X) function returns the character count of string X, excluding any NUL characters for strings (which SQLite typically lacks), or the byte count for blobs. If X is NULL, length(X) is also NULL. For numeric X, it returns the length of its string representation.
 4 | 
 5 | ```
 6 | select length('hello');
 7 | -> 5
 8 | 
 9 | select length(x'ff00ee');
10 | -> 3
11 | 
12 | select length(NULL);
13 | -> NULL
14 | ```
15 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/libsql_vector_idx.md:
--------------------------------------------------------------------------------
1 | libsql_vector_idx(X)
2 | 
3 | Use the **libsql_vector_idx** expression in the CREATE INDEX statement to create an ANN index.
4 | 
5 | ```
6 | CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding));
7 | ```
8 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/like.md:
--------------------------------------------------------------------------------
 1 | like(X,Y), like(X,Y,Z)
 2 | 
 3 | The like() function checks if the string Y matches the pattern X in the "Y LIKE X [ESCAPE Z]" expression.
 4 | 
 5 | ```
 6 | select like('hel%', 'hello')
 7 | -> 1
 8 | 
 9 | select like('wor%', 'hello')
10 | -> 0
11 | ```
12 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/lower.md:
--------------------------------------------------------------------------------
1 | lower(X)
2 | 
3 | The lower(X) function converts all ASCII characters in string X to lowercase.
4 | 
5 | ```
6 | select lower('Hello World');
7 | -> 'hello world'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/ltrim.md:
--------------------------------------------------------------------------------
 1 | ltrim(X,Y)
 2 | 
 3 | The ltrim(X, Y) function removes characters specified in Y from the left side of string X. Omitting Y removes spaces from the left side of X.
 4 | 
 5 | ```
 6 | select ltrim(' hello');
 7 | -> 'hello'
 8 | 
 9 | select ltrim('0.005', '0.');
10 | -> '5'
11 | ```
12 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/max.md:
--------------------------------------------------------------------------------
 1 | max(X,Y,...)
 2 | 
 3 | The multi-argument max() function returns the maximum value among its arguments, or NULL if any argument is NULL. It uses the collating function of the first argument for string comparisons, defaulting to the BINARY collating function if none is specified. When given a single argument, max() acts as an aggregate function.
 4 | 
 5 | ```
 6 | select max(5, 6, 1);
 7 | -> 6
 8 | 
 9 | select max(age) from users;
10 | ```
11 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/min.md:
--------------------------------------------------------------------------------
 1 | max(X,Y,...)
 2 | 
 3 | The multi-argument min() function returns the minimum value among its arguments, utilizing the collating function of the first argument for string comparisons or defaulting to BINARY. When given a single argument, min() acts as an aggregate function.
 4 | 
 5 | ```
 6 | select min(5, 6, 1);
 7 | -> 1
 8 | 
 9 | select min(age) from users;
10 | ```
11 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/nullif.md:
--------------------------------------------------------------------------------
 1 | nullif(X,Y)
 2 | 
 3 | The nullif(X, Y) function returns X if X and Y are different, and NULL if they are the same.
 4 | 
 5 | ```
 6 | select nullif(6, 6)
 7 | -> NULL
 8 | 
 9 | select nullif(7, 6)
10 | -> 7
11 | ```
12 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/octet_length.md:
--------------------------------------------------------------------------------
 1 | octet_length(X)
 2 | 
 3 | The octet_length(X) function returns the number of bytes in the encoding of text string X. If X is NULL, it returns NULL. For BLOBs, it returns the same as length(X). For numeric values, it returns the byte count of the text representation.
 4 | 
 5 | ```
 6 | select octet_length('វិសាល'); -> 15
 7 | select octet_length('visal'); -> 5
 8 | select octet_length(x'ff11ee');  -> 3
 9 | ```
10 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/printf.md:
--------------------------------------------------------------------------------
1 | printf(FORMAT,...)
2 | 
3 | The printf() function, similar to C's printf(), uses a format string (first argument) to construct the output with values from subsequent arguments.
4 | 
5 | ```
6 | select printf('i am %d years old', 50);
7 | -> 'i am 50 years old'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/quote.md:
--------------------------------------------------------------------------------
 1 | quote(X)
 2 | 
 3 | The quote(X) function returns the SQL literal for X, suitable for inclusion in an SQL statement. Strings are single-quoted with escaped interior quotes. BLOBs are encoded as hex literals.
 4 | 
 5 | ```
 6 | select quote('hello '' world')
 7 | -> 'hello '' world'
 8 | 
 9 | select quote(x'ffee00')
10 | -> X'FFEE00'
11 | ```
12 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/random.md:
--------------------------------------------------------------------------------
1 | random()
2 | 
3 | The random() function returns a pseudo-random integer between -9223372036854775808 and +9223372036854775807.
4 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/randomblob.md:
--------------------------------------------------------------------------------
1 | randomblob(N)
2 | 
3 | The randomblob(N) function returns an N-byte blob of pseudo-random bytes. If N is less than 1, it returns a 1-byte random blob.
4 | 
5 | ```
6 | select hex(randomblob(3))
7 | -> 35DD3E
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/replace.md:
--------------------------------------------------------------------------------
1 | replace(X,Y,Z)
2 | 
3 | The replace(X, Y, Z) function returns X with every occurrence of Y replaced by Z.
4 | 
5 | ```
6 | select replace('hello world', 'world', 'planet')
7 | -> 'hello planet'
8 | ```
9 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/round.md:
--------------------------------------------------------------------------------
 1 | round(X,Y)
 2 | 
 3 | The round(X, Y) function returns X rounded to Y decimal places. If Y is omitted or negative, it defaults to 0.
 4 | 
 5 | ```
 6 | select round(1.6);
 7 | -> 2
 8 | 
 9 | select round(1.665, 2);
10 | -> 1.67
11 | ```
12 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/rtrim.md:
--------------------------------------------------------------------------------
 1 | rtrim(X,Y)
 2 | 
 3 | The rtrim(X, Y) function removes all characters in Y from the right side of X. If Y is omitted, it removes spaces.
 4 | 
 5 | ```
 6 | select rtrim('hello ');
 7 | -> 'hello'
 8 | 
 9 | select rtrim('5,000', '0,');
10 | -> '5'
11 | ```
12 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/sign.md:
--------------------------------------------------------------------------------
 1 | sign(X)
 2 | 
 3 | The sign(X) function returns -1, 0, or 1 if X is negative, zero, or positive, respectively. If X is NULL or not a number, it returns NULL.
 4 | 
 5 | ```
 6 | select sign(-5);  -> -1
 7 | select sign(0);   -> 0
 8 | select sign(5);   -> 1
 9 | ```
10 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/string_agg.md:
--------------------------------------------------------------------------------
1 | string_agg(X, Y)
2 | 
3 | The function concatenates all non-NULL values of X into a string, using Y as the separator. If Y is omitted, a comma (",") is used.
4 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/sum.md:
--------------------------------------------------------------------------------
1 | sum(X)
2 | 
3 | The function returns the sum of all non-NULL values in the group, returning NULL if there are no non-NULL inputs.
4 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/total.md:
--------------------------------------------------------------------------------
1 | total(X)
2 | 
3 | The function returns the sum of all non-NULL values in the group, with a result of 0.0 if there are no non-NULL inputs.
4 | The result of total() is always a floating point value.
5 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/vector.md:
--------------------------------------------------------------------------------
1 | vector(X)
2 | 
3 | Function to convert a vector from string format to binary.
4 | 
5 | ```
6 | INSERT INTO movies (title, year, embedding) VALUES('Napoleon', 2023, vector('[1,2,3]'));
7 | ```
8 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/vector_distance_cos.md:
--------------------------------------------------------------------------------
 1 | vector_distance_cos(X, Y)
 2 | 
 3 | Function to calculate cosine distance between two vectors.
 4 | It computes the distance as 1 minus the cosine similarity,
 5 | meaning a smaller distance indicates closer vectors.
 6 | 
 7 | ```
 8 | SELECT * FROM movie
 9 | ORDER BY vector_distance_cos(embedding, '[3,1,2]')
10 | ```
11 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/vector_extract.md:
--------------------------------------------------------------------------------
 1 | vector_extract(X)
 2 | 
 3 | Function to extract string from binary vector
 4 | 
 5 | ```
 6 | SELECT title,
 7 |   vector_extract(embedding),
 8 |   vector_distance_cos(embedding, vector('[5,6,7]'))
 9 | FROM movies;
10 | ```
11 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/functions/vector_top_k.md:
--------------------------------------------------------------------------------
 1 | vector_top_k(idx_name, q_vector, k)
 2 | 
 3 | Use **vector_top_k** with the **idx_name** index to efficiently find the top k most similar vectors to **q_vector**
 4 | 
 5 | ```
 6 | SELECT title, year
 7 | FROM vector_top_k('movies_idx', vector('[4,5,6]'), 3)
 8 | JOIN movies ON movies.rowid = id
 9 | ```
10 | 


--------------------------------------------------------------------------------
/src/drivers/sqlite/sql-parse-view.ts:
--------------------------------------------------------------------------------
 1 | import { tokenizeSql } from "@outerbase/sdk-transform";
 2 | import { DatabaseViewSchema } from "../base-driver";
 3 | import { CursorV2 } from "./sql-parse-table";
 4 | 
 5 | export function parseCreateViewScript(
 6 |   schemaName: string,
 7 |   sql: string
 8 | ): DatabaseViewSchema {
 9 |   const cursor = new CursorV2(tokenizeSql(sql, "sqlite"));
10 | 
11 |   cursor.expectToken("CREATE");
12 |   cursor.expectTokenOptional("TEMP");
13 |   cursor.expectTokenOptional("TEMPORARY");
14 |   cursor.expectToken("VIEW");
15 |   cursor.expectTokensOptional(["IF", "NOT", "EXIST"]);
16 | 
17 |   const name = cursor.consumeIdentifier();
18 | 
19 |   cursor.expectToken("AS");
20 | 
21 |   let statement = "";
22 |   const fromStatement = cursor.getPointer();
23 |   let toStatement;
24 | 
25 |   while (!cursor.end()) {
26 |     toStatement = cursor.getPointer();
27 | 
28 |     if (cursor.match(";")) {
29 |       break;
30 |     }
31 | 
32 |     cursor.next();
33 |   }
34 | 
35 |   if (fromStatement && toStatement) {
36 |     statement = cursor.toStringRange(fromStatement, toStatement);
37 |   }
38 | 
39 |   return {
40 |     schemaName,
41 |     name,
42 |     statement,
43 |   };
44 | }
45 | 


--------------------------------------------------------------------------------
/src/env.ts:
--------------------------------------------------------------------------------
 1 | import { createEnv } from "@t3-oss/env-nextjs";
 2 | import { z } from "zod";
 3 | 
 4 | import pkg from "./../package.json";
 5 | 
 6 | export const appVersion = pkg.version;
 7 | 
 8 | export const env = createEnv({
 9 |   server: {
10 |     BASE_URL: z.string().min(1).optional(),
11 | 
12 |     DATABASE_URL: z.string().min(1).optional(),
13 |     DATABASE_AUTH_TOKEN: z.string().min(1).optional(),
14 | 
15 |     DATABASE_ANALYTIC_URL: z.string().optional(),
16 |     DATABASE_ANALYTIC_AUTH_TOKEN: z.string().optional(),
17 | 
18 |     ENCRYPTION_KEY: z.string().min(30).optional(),
19 |   },
20 | 
21 |   experimental__runtimeEnv: {
22 |     DATABASE_URL: process.env.DATABASE_URL,
23 |     DATABASE_AUTH_TOKEN: process.env.DATABASE_AUTH_TOKEN,
24 |     GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
25 |     GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
26 |     ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
27 |     GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
28 |     GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
29 |     DATABASE_ANALYTIC_URL: process.env.DATABASE_ANALYTIC_URL,
30 |     DATABASE_ANALYTIC_AUTH_TOKEN: process.env.DATABASE_ANALYTIC_AUTH_TOKEN,
31 |   },
32 | });
33 | 


--------------------------------------------------------------------------------
/src/extensions/data-catalog/sidebar.tsx:
--------------------------------------------------------------------------------
 1 | import { SidebarMenuHeader, SidebarMenuItem } from "@/components/sidebar-menu";
 2 | import { Database } from "@phosphor-icons/react";
 3 | import { dataCatalogModelTab, dataCatalogTab } from ".";
 4 | 
 5 | export default function DataCatalogSidebar() {
 6 |   return (
 7 |     <div className="flex flex-1 flex-col">
 8 |       <SidebarMenuHeader text="Data Catalog" />
 9 |       <SidebarMenuItem
10 |         text="Data Catalog"
11 |         onClick={() => dataCatalogTab.open({})}
12 |         icon={Database}
13 |       />
14 |       <SidebarMenuItem
15 |         text="Data Model"
16 |         onClick={() => dataCatalogModelTab.open({})}
17 |         icon={Database}
18 |       />
19 |     </div>
20 |   );
21 | }
22 | 


--------------------------------------------------------------------------------
/src/extensions/data-catalog/term-definition-list.tsx:
--------------------------------------------------------------------------------
 1 | import { Edit3 } from "lucide-react";
 2 | import { DataCatalogTermDefinition } from "./driver";
 3 | 
 4 | interface Props {
 5 |   onSelect: (item: DataCatalogTermDefinition) => void;
 6 |   data: DataCatalogTermDefinition[];
 7 | }
 8 | export default function TermDefinitionList({ data, onSelect }: Props) {
 9 |   return (
10 |     <div className="mt-10">
11 |       {data?.map((item) => {
12 |         return (
13 |           <div
14 |             onClick={() => onSelect(item)}
15 |             key={item.id}
16 |             className="bg-accent mt-3 flex rounded-xl border p-5 hover:bg-gray-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
17 |           >
18 |             <div className="gap-5">
19 |               <div className="text-lg font-bold">{item.name}</div>
20 |               <div>{item.definition}</div>
21 |               {item.otherNames && (
22 |                 <div className="mt-3 text-sm">
23 |                   Also known as: {item.otherNames}
24 |                 </div>
25 |               )}
26 |             </div>
27 |             <div className="flex-1" />
28 |             <Edit3 size={18} />
29 |           </div>
30 |         );
31 |       })}
32 |     </div>
33 |   );
34 | }
35 | 


--------------------------------------------------------------------------------
/src/extensions/data-decorator/index.tsx:
--------------------------------------------------------------------------------
 1 | import { StudioExtension } from "@/core/extension-base";
 2 | import { StudioExtensionContext } from "@/core/extension-manager";
 3 | import { DecoratorEditor } from "./editor";
 4 | 
 5 | export default class DataDecoratorExtension extends StudioExtension {
 6 |   extensionName = "data-decorator";
 7 | 
 8 |   init(studio: StudioExtensionContext): void {
 9 |     studio.registerQueryHeaderContextMenu((header, state) => {
10 |       return {
11 |         key: "data-decorator",
12 |         title: "Decorate",
13 |         component: <DecoratorEditor header={header} state={state} />,
14 |       };
15 |     });
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/src/extensions/dolt/index.tsx:
--------------------------------------------------------------------------------
 1 | import { DoltIcon } from "@/components/icons/outerbase-icon";
 2 | import { StudioExtension } from "@/core/extension-base";
 3 | import { StudioExtensionContext } from "@/core/extension-manager";
 4 | import DoltSidebar from "./dolt-sidebar";
 5 | import { Table } from "lucide-react";
 6 | import { createTabExtension } from "@/core/extension-tab";
 7 | 
 8 | export const doltCommitTab = createTabExtension<{
 9 |   commitHash: string;
10 |   commitMessage: string;
11 | }>({
12 |   name: "dolt-commit",
13 |   key: (options) => options?.commitHash ?? "",
14 |   generate: (options) => ({
15 |     title: "Commit " + options?.commitMessage,
16 |     icon: Table,
17 |     component: <div>Testing</div>,
18 |   }),
19 | });
20 | 
21 | export default class DoltExtension extends StudioExtension {
22 |   extensionName = "dolt";
23 | 
24 |   init(studio: StudioExtensionContext): void {
25 |     studio.registerSidebar({
26 |       key: "dolt",
27 |       name: "Dolt",
28 |       icon: <DoltIcon className="w-7 h-7 text-green-500" />,
29 |       content: <DoltSidebar />,
30 |     });
31 |   }
32 | }
33 | 


--------------------------------------------------------------------------------
/src/extensions/local-setting-sidebar/index.tsx:
--------------------------------------------------------------------------------
 1 | import { localSettingDialog } from "@/app/(outerbase)/local-setting-dialog";
 2 | import { StudioExtension } from "@/core/extension-base";
 3 | import { StudioExtensionContext } from "@/core/extension-manager";
 4 | import { MagicWand } from "@phosphor-icons/react";
 5 | 
 6 | export default class LocalSettingSidebar extends StudioExtension {
 7 |   extensionName = "local-setting-sidebar";
 8 | 
 9 |   init(studio: StudioExtensionContext): void {
10 |     studio.registerSidebar({
11 |       key: "local-setting-sidebar",
12 |       name: "Local Settings",
13 |       icon: <MagicWand size={24} />,
14 |       onClick: () => {
15 |         localSettingDialog.show({}).then().catch();
16 |       },
17 |     });
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/src/extensions/outerbase/index.tsx:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Outerbase Cloud extension for studio functionality.
 3 |  */
 4 | import { StudioExtension } from "@/core/extension-base";
 5 | import { StudioExtensionContext } from "@/core/extension-manager";
 6 | import { OuterbaseDatabaseConfig } from "@/outerbase-cloud/api-type";
 7 | import { updateOuterbaseSchemas } from "@/outerbase-cloud/api-workspace";
 8 | 
 9 | export default class OuterbaseExtension extends StudioExtension {
10 |   extensionName = "outerbase-cloud-studio";
11 | 
12 |   constructor(protected config: OuterbaseDatabaseConfig) {
13 |     super();
14 |   }
15 | 
16 |   init(studio: StudioExtensionContext): void {
17 |     // The Outerbase Data Catalog and AI-powered features
18 |     // require the schema to be stored in the Outerbase API database.
19 |     // Originally, Outerbase fetched the schema from an API endpoint
20 |     // and stored it in its database.
21 |     //
22 |     // Our studio differs because we obtain the schema from
23 |     // raw queries. The Outerbase API does not know when to refetch
24 |     // the updated schema and store it in its database.
25 |     // We hook into the event when the studio finishes fetching the schema
26 |     // and notify Outerbase Cloud to refetch the schema on their side.
27 |     //
28 |     // TECHNICAL DEBT: This is redundant work and should be optimized in the future.
29 |     studio.registerAfterFetchSchema(() => {
30 |       updateOuterbaseSchemas(this.config.workspaceId, this.config.sourceId)
31 |         .then()
32 |         .catch();
33 |     });
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/src/extensions/query-console-log/index.ts:
--------------------------------------------------------------------------------
 1 | import { StudioExtension } from "@/core/extension-base";
 2 | import { StudioExtensionContext } from "@/core/extension-manager";
 3 | 
 4 | export default class QueryHistoryConsoleLogExtension extends StudioExtension {
 5 |   extensionName = "query-history-console-log";
 6 | 
 7 |   init(studio: StudioExtensionContext): void {
 8 |     studio.registerBeforeQuery(async (payload) => {
 9 |       const statements = payload.getStatments();
10 | 
11 |       if (statements.length === 1) {
12 |         console.group("Query");
13 |         console.info(`%c${statements[0]}`, "color:#e67e22");
14 |         console.groupEnd();
15 |       } else {
16 |         console.group("Transaction");
17 |         statements.forEach((s) => console.log(`%c${s}`, "color:#e67e22"));
18 |         console.groupEnd();
19 |       }
20 |     });
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/src/extensions/view-editor/index.tsx:
--------------------------------------------------------------------------------
 1 | import { StudioExtension } from "@/core/extension-base";
 2 | import { createTabExtension } from "@/core/extension-tab";
 3 | import ViewTab from "./view-tab";
 4 | import { LucideView } from "lucide-react";
 5 | import { StudioExtensionContext } from "@/core/extension-manager";
 6 | 
 7 | export const viewEditorExtensionTab = createTabExtension<{
 8 |   schemaName?: string;
 9 |   name?: string;
10 | }>({
11 |   name: "view",
12 |   key: (options) => {
13 |     return `${options.schemaName}.${options.name}`;
14 |   },
15 |   generate: (options) => ({
16 |     title: options.name || "New View",
17 |     component: (
18 |       <ViewTab schemaName={options.schemaName} name={options.name ?? ""} />
19 |     ),
20 |     icon: LucideView,
21 |   }),
22 | });
23 | 
24 | export default class ViewEditorExtension extends StudioExtension {
25 |   extensionName = "view-editor";
26 | 
27 |   init(studio: StudioExtensionContext): void {
28 |     studio.registerCreateResourceMenu({
29 |       key: "view",
30 |       title: "Create View",
31 |       onClick: () => {
32 |         viewEditorExtensionTab.open({});
33 |       },
34 |     });
35 | 
36 |     studio.registerResourceContextMenu((resource) => {
37 |       if (resource.type !== "view") return;
38 |       return {
39 |         key: "view",
40 |         title: "Edit View",
41 |         onClick: () => {
42 |           viewEditorExtensionTab.open({
43 |             schemaName: resource.schemaName,
44 |             name: resource.name,
45 |           });
46 |         },
47 |       };
48 |     }, "modification");
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/src/lib/ai-agent-storage.ts:
--------------------------------------------------------------------------------
 1 | import AgentDriverList from "@/drivers/agent/list";
 2 | import { BaseDriver } from "@/drivers/base-driver";
 3 | import { useMemo } from "react";
 4 | import useSWR, { mutate } from "swr";
 5 | 
 6 | export interface LocalAgentType {
 7 |   provider: "openai";
 8 |   model: "gpt-4o-mini";
 9 |   token: string;
10 | }
11 | 
12 | export function getAgentFromLocalStorage(): LocalAgentType | undefined {
13 |   if (typeof window === "undefined") return undefined;
14 | 
15 |   // Getting the driver from the local storage
16 |   const agentRawData = localStorage.getItem("agent");
17 |   if (!agentRawData) return undefined;
18 | 
19 |   // Parsing the agent data
20 |   const agentData: LocalAgentType = JSON.parse(agentRawData);
21 | 
22 |   // Validate the data
23 |   if (agentData.provider !== "openai") return undefined;
24 |   if (agentData.model !== "gpt-4o-mini") return undefined;
25 |   if (!agentData.token) return undefined;
26 | 
27 |   return agentData;
28 | }
29 | 
30 | export function updateAgentFromLocalStorage(data: LocalAgentType) {
31 |   localStorage.setItem("agent", JSON.stringify(data));
32 |   mutate("/local-agent-setting", data);
33 | }
34 | 
35 | export function useAvailableAIAgents(databaseDriver?: BaseDriver | null) {
36 |   const { data: agentConfig } = useSWR(
37 |     "/local-agent-setting",
38 |     getAgentFromLocalStorage
39 |   );
40 | 
41 |   return useMemo(() => {
42 |     if (!databaseDriver) return undefined;
43 |     return new AgentDriverList(databaseDriver, agentConfig?.token);
44 |   }, [databaseDriver, agentConfig]);
45 | }
46 | 


--------------------------------------------------------------------------------
/src/lib/bit-operation.ts:
--------------------------------------------------------------------------------
 1 | const byteToHex: string[] = [];
 2 | 
 3 | for (let n = 0; n <= 0xff; ++n) {
 4 |   const hexOctet = n.toString(16).padStart(2, "0");
 5 |   byteToHex.push(hexOctet.toUpperCase());
 6 | }
 7 | 
 8 | // https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex
 9 | export function hex(arrayBuffer: ArrayBuffer) {
10 |   const buff = new Uint8Array(arrayBuffer);
11 |   let hexOctets = "";
12 | 
13 |   for (let i = 0; i < buff.length; ++i) hexOctets += byteToHex[buff[i]];
14 |   return hexOctets;
15 | }
16 | 


--------------------------------------------------------------------------------
/src/lib/convertNumber.ts:
--------------------------------------------------------------------------------
 1 | export const formatNumber = (n: number | undefined) => {
 2 |   if (!n) return n;
 3 |   return new Intl.NumberFormat("en-US", {
 4 |     style: "decimal",
 5 |     minimumFractionDigits: 0,
 6 |     maximumFractionDigits: 2,
 7 |   }).format(n);
 8 | };
 9 | 
10 | export function convertTimeToMilliseconds(time: string) {
11 |   const extension = time.charAt(time.length - 1);
12 |   const value = time.substring(0, time.length-1);
13 |   
14 |   if(!isNaN(Number(value))) {
15 |     switch(extension) {
16 |       case 's':
17 |         return Number(value) * 1000;
18 |       case 'm':
19 |         return (Number(value) * 60) * 1000;
20 |       case 'h':
21 |         return (Number(value) * 60 * 60) * 1000;
22 |       case 'd':
23 |         return ((Number(value) * 24) * 60 * 60) * 1000;
24 |       default:
25 |         return 0;
26 |       }
27 |   }
28 |   return 0;
29 | }


--------------------------------------------------------------------------------
/src/lib/dnd-kit.ts:
--------------------------------------------------------------------------------
 1 | import { Modifier } from "@dnd-kit/core";
 2 | 
 3 | type Transform = {
 4 |   x: number;
 5 |   y: number;
 6 |   scaleX: number;
 7 |   scaleY: number;
 8 | };
 9 | 
10 | interface Transition {
11 |   property: string;
12 |   easing: string;
13 |   duration: number;
14 | }
15 | 
16 | export const CSS = Object.freeze({
17 |   Translate: {
18 |     toString(transform: Transform | null) {
19 |       if (!transform) {
20 |         return;
21 |       }
22 | 
23 |       const { x, y } = transform;
24 | 
25 |       return `translate3d(${x ? Math.round(x) : 0}px, ${
26 |         y ? Math.round(y) : 0
27 |       }px, 0)`;
28 |     },
29 |   },
30 |   Scale: {
31 |     toString(transform: Transform | null) {
32 |       if (!transform) {
33 |         return;
34 |       }
35 | 
36 |       const { scaleX, scaleY } = transform;
37 | 
38 |       return `scaleX(${scaleX}) scaleY(${scaleY})`;
39 |     },
40 |   },
41 |   Transform: {
42 |     toString(transform: Transform | null) {
43 |       if (!transform) {
44 |         return;
45 |       }
46 | 
47 |       return [
48 |         CSS.Translate.toString(transform),
49 |         CSS.Scale.toString(transform),
50 |       ].join(" ");
51 |     },
52 |   },
53 |   Transition: {
54 |     toString({ property, duration, easing }: Transition) {
55 |       return `${property} ${duration}ms ${easing}`;
56 |     },
57 |   },
58 | });
59 | 
60 | export const restrictToHorizontalAxis: Modifier = ({ transform }) => {
61 |   return {
62 |     ...transform,
63 |     y: 0,
64 |   };
65 | };
66 | 


--------------------------------------------------------------------------------
/src/lib/download-file.ts:
--------------------------------------------------------------------------------
 1 | function extractCredentialsAndPrepareHeaders(url: string) {
 2 |   const parsedUrl = new URL(url);
 3 | 
 4 |   // Extract credentials from the URL
 5 |   const { username, password } = parsedUrl;
 6 | 
 7 |   // Remove username and password from the URL to clean it
 8 |   parsedUrl.username = "";
 9 |   parsedUrl.password = "";
10 | 
11 |   // Prepare the Authorization header
12 |   let options = {};
13 | 
14 |   if (username || password) {
15 |     const base64Credentials = btoa(`${username}:${password}`);
16 |     options = {
17 |       headers: { Authorization: `Basic ${base64Credentials}` },
18 |     };
19 |   }
20 |   // Return the cleaned URL and the headers
21 |   return {
22 |     cleanedUrl: parsedUrl.href,
23 |     options,
24 |   };
25 | }
26 | 
27 | export default function downloadFileFromUrl(url: string) {
28 |   const { cleanedUrl, options } = extractCredentialsAndPrepareHeaders(url);
29 |   return fetch(cleanedUrl, options).then((r) => r.arrayBuffer());
30 | }
31 | 


--------------------------------------------------------------------------------
/src/lib/empty-state.ts:
--------------------------------------------------------------------------------
 1 | import { DatabaseResultStat } from "@/drivers/base-driver";
 2 | 
 3 | export default function isEmptyResultStats(stats: DatabaseResultStat) {
 4 |   return (
 5 |     !stats.queryDurationMs &&
 6 |     !stats.rowsAffected &&
 7 |     !stats.rowsRead &&
 8 |     !stats.rowsWritten
 9 |   );
10 | }
11 | 


--------------------------------------------------------------------------------
/src/lib/encryption.ts:
--------------------------------------------------------------------------------
 1 | import crypto from "crypto";
 2 | 
 3 | const RANDOM_IV_LENGTH = 12;
 4 | const AUTH_TAG_SIZE = 16;
 5 | 
 6 | export function encrypt(key: Buffer, text: string) {
 7 |   const iv = crypto.randomBytes(RANDOM_IV_LENGTH);
 8 |   const cipher = crypto.createCipheriv("aes-256-gcm", key, iv, {
 9 |     authTagLength: AUTH_TAG_SIZE,
10 |   });
11 | 
12 |   return Buffer.concat([
13 |     iv,
14 |     cipher.update(Buffer.from(text)),
15 |     cipher.final(),
16 |     cipher.getAuthTag(),
17 |   ]).toString("base64");
18 | }
19 | 
20 | export function decrypt(key: Buffer, base64: string) {
21 |   const e = Buffer.from(base64, "base64");
22 |   const iv = e.subarray(0, RANDOM_IV_LENGTH);
23 |   const content = e.subarray(RANDOM_IV_LENGTH, -AUTH_TAG_SIZE);
24 |   const tag = e.subarray(-AUTH_TAG_SIZE);
25 | 
26 |   const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
27 |   decipher.setAuthTag(tag);
28 |   const plain = Buffer.concat([decipher.update(content), decipher.final()]);
29 |   return plain.toString();
30 | }
31 | 


--------------------------------------------------------------------------------
/src/lib/generate-id.ts:
--------------------------------------------------------------------------------
 1 | export function generateId() {
 2 |   if (crypto.randomUUID) {
 3 |     return crypto.randomUUID();
 4 |   }
 5 | 
 6 |   // https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid/2117523#2117523
 7 |   return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
 8 |     (
 9 |       +c ^
10 |       (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))
11 |     ).toString(16)
12 |   );
13 | }
14 | 


--------------------------------------------------------------------------------
/src/lib/json-safe.ts:
--------------------------------------------------------------------------------
 1 | export default function parseSafeJson<T = unknown>(
 2 |   str: string | null | undefined,
 3 |   defaultValue: T
 4 | ): T {
 5 |   if (!str) return defaultValue;
 6 | 
 7 |   try {
 8 |     // eslint-disable-next-line @typescript-eslint/no-unsafe-return
 9 |     return JSON.parse(str);
10 |   } catch {
11 |     return defaultValue;
12 |   }
13 | }
14 | 


--------------------------------------------------------------------------------
/src/lib/sql/sql-generate.schema.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   DatabaseTableColumnChange,
 3 |   DatabaseTableSchema,
 4 |   DatabaseTableSchemaChange,
 5 | } from "@/drivers/base-driver";
 6 | import deepEqual from "deep-equal";
 7 | import { cloneDeep } from "lodash";
 8 | import { generateId } from "../generate-id";
 9 | 
10 | export function checkSchemaColumnChange(change: DatabaseTableColumnChange) {
11 |   return !deepEqual(change.old, change.new);
12 | }
13 | 
14 | export function checkSchemaChange(change: DatabaseTableSchemaChange) {
15 |   if (change.name.new !== change.name.old) return true;
16 | 
17 |   for (const col of change.columns) {
18 |     if (checkSchemaColumnChange(col)) {
19 |       return true;
20 |     }
21 |   }
22 | 
23 |   return false;
24 | }
25 | 
26 | export function createTableSchemaDraft(
27 |   schemaName: string,
28 |   schema: DatabaseTableSchema
29 | ): DatabaseTableSchemaChange {
30 |   return {
31 |     schemaName,
32 |     name: {
33 |       old: schema.tableName,
34 |       new: schema.tableName,
35 |     },
36 |     columns: schema.columns.map((col) => ({
37 |       key: generateId(),
38 |       old: col,
39 |       new: cloneDeep(col),
40 |     })),
41 |     constraints: (schema.constraints ?? []).map((con) => ({
42 |       id: generateId(),
43 |       old: con,
44 |       new: cloneDeep(con),
45 |     })),
46 |     createScript: schema.createScript,
47 |   };
48 | }
49 | 


--------------------------------------------------------------------------------
/src/lib/tracking-throttle.ts:
--------------------------------------------------------------------------------
 1 | const THROTTLE_COUNT: Record<string, number> = {};
 2 | const THROTTLE_TIMESTAMP: Record<string, number> = {};
 3 | 
 4 | /**
 5 |  * Throttle event by name by number of limit event in interval
 6 |  *
 7 |  * @param name Event name
 8 |  * @param time Number of limit event in interval
 9 |  * @param interval Time interval in milliseconds before reset the throttle
10 |  * @returns
11 |  */
12 | export function throttleEvent(name: string, time: number, interval: number) {
13 |   console.log(
14 |     "throttleEvent",
15 |     THROTTLE_COUNT,
16 |     THROTTLE_TIMESTAMP,
17 |     name,
18 |     (THROTTLE_COUNT[name] ?? 0) > time
19 |   );
20 | 
21 |   if (Date.now() - (THROTTLE_TIMESTAMP[name] ?? 0) < interval) {
22 |     THROTTLE_COUNT[name] = (THROTTLE_COUNT[name] ?? 0) + 1;
23 |     return (THROTTLE_COUNT[name] ?? 0) > time;
24 |   } else {
25 |     THROTTLE_COUNT[name] = 0;
26 |     THROTTLE_TIMESTAMP[name] = Date.now();
27 |   }
28 | 
29 |   return false;
30 | }
31 | 


--------------------------------------------------------------------------------
/src/lib/type-utils.ts:
--------------------------------------------------------------------------------
 1 | export type Prettify<T> = {
 2 |   [K in keyof T]: T[K];
 3 |   // eslint-disable-next-line @typescript-eslint/ban-types
 4 | } & {};
 5 | 
 6 | /**
 7 |  * Make some properties of T optional
 8 |  *
 9 |  * @example
10 |  * type User = {
11 |  *  id: string;
12 |  *  name: string;
13 |  *  age: number;
14 |  * };
15 |  *
16 |  * type PartialUser = MakeOptional<User, "age" | "name">;
17 |  * // { id: string; name?: string | undefined; age?: number | undefined; }
18 |  *
19 |  */
20 | export type MakeOptional<T, K extends keyof T> = Prettify<
21 |   Omit<T, K> & Partial<Pick<T, K>>
22 | >;
23 | 


--------------------------------------------------------------------------------
/src/lib/utils-datetime.ts:
--------------------------------------------------------------------------------
 1 | // https://stackoverflow.com/a/3177838
 2 | export function timeSince(timestamp: number) {
 3 |   const seconds = Math.floor((new Date().getTime() - timestamp) / 1000);
 4 | 
 5 |   let interval = seconds / 31536000;
 6 | 
 7 |   if (interval > 1) {
 8 |     return Math.floor(interval) + " years";
 9 |   }
10 | 
11 |   interval = seconds / 2592000;
12 |   if (interval >= 1 && interval < 2) {
13 |     return Math.floor(interval) + " month";
14 |   } else if (interval >= 2) {
15 |     return Math.floor(interval) + " months";
16 |   }
17 | 
18 |   interval = seconds / 86400;
19 |   if (interval >= 1 && interval < 2) {
20 |     return Math.floor(interval) + " day";
21 |   } else if (interval >= 2) {
22 |     return Math.floor(interval) + " days";
23 |   }
24 | 
25 |   interval = seconds / 3600;
26 |   if (interval >= 1 && interval < 2) {
27 |     return Math.floor(interval) + " hour";
28 |   } else if (interval >= 2) {
29 |     return Math.floor(interval) + " hours";
30 |   }
31 | 
32 |   interval = seconds / 60;
33 |   if (interval >= 1 && interval < 2) {
34 |     return Math.floor(interval) + " minute";
35 |   } else if (interval >= 2) {
36 |     return Math.floor(interval) + " minutes";
37 |   }
38 | 
39 |   return Math.floor(seconds) + " seconds";
40 | }
41 | 


--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
 1 | import { type ClassValue, clsx } from "clsx";
 2 | import { twMerge } from "tailwind-merge";
 3 | 
 4 | export function cn(...inputs: ClassValue[]) {
 5 |   return twMerge(clsx(inputs));
 6 | }
 7 | export interface ApiErrorResponse {
 8 |   message: string;
 9 |   detailedMessage?: string;
10 | }
11 | 
12 | export function strippedWorkspaceName(value: string) {
13 |   return value
14 |     .replace(
15 |       /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
16 |       ""
17 |     )
18 |     .replace(/[^a-zA-Z 0-9]/g, "")
19 |     .replace(/[^A-Z0-9]/gi, "-")
20 |     ?.toLowerCase();
21 | }
22 | 


--------------------------------------------------------------------------------
/src/mdx-components.tsx:
--------------------------------------------------------------------------------
 1 | import type { MDXComponents } from "mdx/types";
 2 | import CodeBlock from "./components/code-block";
 3 | 
 4 | export function useMDXComponents(components: MDXComponents): MDXComponents {
 5 |   return {
 6 |     ...components,
 7 |     code: (props) => {
 8 |       return <code {...props} />;
 9 |     },
10 |     pre: CodeBlock,
11 |   };
12 | }
13 | 


--------------------------------------------------------------------------------
/src/outerbase-cloud/api-board.ts:
--------------------------------------------------------------------------------
 1 | import { ChartParams, ChartValue } from "@/components/chart/chart-type";
 2 | import { requestOuterbase } from "./api";
 3 | 
 4 | export async function createOuterbaseDashboardChart(
 5 |   workspaceId: string,
 6 |   options: {
 7 |     type: string;
 8 |     source_id: string;
 9 |     params: ChartParams;
10 |     name: string;
11 |   }
12 | ): Promise<ChartValue> {
13 |   return requestOuterbase<ChartValue>(
14 |     `/api/v1/workspace/${workspaceId}/chart`,
15 |     "POST",
16 |     options
17 |   );
18 | }
19 | 
20 | export async function updateOuterbaseDashboardChart(
21 |   workspaceId: string,
22 |   chartId: string,
23 |   options: {
24 |     type: string;
25 |     source_id: string;
26 |     params: ChartParams;
27 |     name: string;
28 |   }
29 | ): Promise<ChartValue>{
30 |   return requestOuterbase<ChartValue>(
31 |     `/api/v1/workspace/${workspaceId}/chart/${chartId}`, 'PUT', options
32 |   )
33 | }
34 | 


--------------------------------------------------------------------------------
/src/outerbase-cloud/database/query.ts:
--------------------------------------------------------------------------------
 1 | import { DatabaseResultSet, QueryableBaseDriver } from "@/drivers/base-driver";
 2 | import { runOuterbaseQueryBatch, runOuterbaseQueryRaw } from "../api";
 3 | import { OuterbaseDatabaseConfig } from "../api-type";
 4 | import { transformOuterbaseResult } from "./utils";
 5 | 
 6 | export class OuterbaseQueryable implements QueryableBaseDriver {
 7 |   protected workspaceId: string;
 8 |   protected sourceId: string;
 9 | 
10 |   constructor({ workspaceId, sourceId }: OuterbaseDatabaseConfig) {
11 |     this.workspaceId = workspaceId;
12 |     this.sourceId = sourceId;
13 |   }
14 | 
15 |   async query(stmt: string): Promise<DatabaseResultSet> {
16 |     const jsonResponse = await runOuterbaseQueryRaw(
17 |       this.workspaceId,
18 |       this.sourceId,
19 |       stmt
20 |     );
21 | 
22 |     return transformOuterbaseResult(jsonResponse);
23 |   }
24 | 
25 |   async batch(stmts: string[]): Promise<DatabaseResultSet[]> {
26 |     return (
27 |       await runOuterbaseQueryBatch(this.workspaceId, this.sourceId, stmts)
28 |     ).map(transformOuterbaseResult);
29 |   }
30 | 
31 |   async transaction(stmts: string[]): Promise<DatabaseResultSet[]> {
32 |     return this.batch(stmts);
33 |   }
34 | }
35 | 


--------------------------------------------------------------------------------
/src/outerbase-cloud/database/utils.ts:
--------------------------------------------------------------------------------
 1 | import { DatabaseResultSet } from "@/drivers/base-driver";
 2 | import MySQLLikeDriver from "@/drivers/mysql/mysql-driver";
 3 | import PostgresLikeDriver from "@/drivers/postgres/postgres-driver";
 4 | import { SqliteLikeBaseDriver } from "@/drivers/sqlite-base-driver";
 5 | import { OuterbaseAPIQueryRaw, OuterbaseDatabaseConfig } from "../api-type";
 6 | import { OuterbaseQueryable } from "./query";
 7 | 
 8 | export function transformOuterbaseResult(
 9 |   result: OuterbaseAPIQueryRaw
10 | ): DatabaseResultSet {
11 |   return {
12 |     rows: result.items,
13 |     headers: result.headers,
14 |     stat: result.stat ?? {
15 |       rowsAffected: 0,
16 |       rowsRead: null,
17 |       rowsWritten: null,
18 |       queryDurationMs: null,
19 |     },
20 |     lastInsertRowid: result.lastInsertRowid,
21 |   };
22 | }
23 | 
24 | export function createOuterbaseDatabaseDriver(
25 |   type: string,
26 |   config: OuterbaseDatabaseConfig
27 | ) {
28 |   const queryable = new OuterbaseQueryable(config);
29 | 
30 |   if (type === "postgres") {
31 |     return new PostgresLikeDriver(queryable);
32 |   } else if (type === "mysql") {
33 |     return new MySQLLikeDriver(queryable);
34 |   }
35 | 
36 |   return new SqliteLikeBaseDriver(queryable);
37 | }
38 | 


--------------------------------------------------------------------------------
/src/window.d.ts:
--------------------------------------------------------------------------------
 1 | import { DatabaseResultSet } from "./drivers/base-driver";
 2 | import { SavedDocNamespace } from "./drivers/saved-doc/saved-doc-driver";
 3 | 
 4 | export {};
 5 | 
 6 | interface OuterbaseIPC {
 7 |   docs?: {
 8 |     load(): Promise<{
 9 |       namespace: SavedDocNamespace[];
10 |       docs: Record<string, SavedDocData[]>;
11 |     } | null>;
12 | 
13 |     save(data: {
14 |       namespace: SavedDocNamespace[];
15 |       docs: Record<string, SavedDocData[]>;
16 |     }): Promise<void>;
17 |   };
18 |   query(statement: string): Promise<DatabaseResultSet>;
19 |   transaction(statements: string[]): Promise<DatabaseResultSet[]>;
20 |   close(): void;
21 | }
22 | 
23 | declare global {
24 |   interface Window {
25 |     outerbaseIpc?: OuterbaseIPC;
26 |     showOuterbaseDialog: Record<
27 |       string,
28 |       (props: {
29 |         component: FunctionComponent;
30 |         options: unknown;
31 |         resolve: (props: unknown) => void;
32 |         defaultCloseValue: unknown;
33 |       }) => void
34 |     >;
35 |   }
36 | }
37 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2020",
 4 |     "lib": ["dom", "dom.iterable", "esnext"],
 5 |     "allowJs": true,
 6 |     "skipLibCheck": true,
 7 |     "strict": true,
 8 |     "noEmit": true,
 9 |     "esModuleInterop": true,
10 |     "module": "esnext",
11 |     "moduleResolution": "bundler",
12 |     "resolveJsonModule": true,
13 |     "isolatedModules": true,
14 |     "jsx": "preserve",
15 |     "incremental": true,
16 |     "plugins": [
17 |       {
18 |         "name": "next"
19 |       }
20 |     ],
21 |     "paths": {
22 |       "@/*": ["./src/*"]
23 |     }
24 |   },
25 |   "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 |   "exclude": ["node_modules"]
27 | }
28 | 


--------------------------------------------------------------------------------
/wrangler.jsonc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "main": ".open-next/worker.js",
 3 |   "name": "outerbase-studio",
 4 |   "compatibility_date": "2024-09-23",
 5 |   "compatibility_flags": ["nodejs_compat"],
 6 |   "assets": {
 7 |     "directory": ".open-next/assets",
 8 |     "binding": "ASSETS"
 9 |   },
10 |   "observability": {
11 |     "enabled": true,
12 |     "head_sampling_rate": 1
13 |   },
14 |   "kv_namespaces": [
15 |     {
16 |       "binding": "NEXT_CACHE_WORKERS_KV",
17 |       "id": "d6857f07f0a04e108dfddba469201edf"
18 |     }
19 |   ]
20 | }
21 | 


--------------------------------------------------------------------------------