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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions e2e/docs-e2e/tests/Docs/integrations-pages-load.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ test('Integrations Drizzle page loads', async ({ page }) => {
await expect(page).toHaveTitle('Drizzle | Integrations 📚 Qwik Documentation');
});

test('Integrations Gel page loads', async ({ page }) => {
await page.goto('/docs/integrations/gel/');
await expect(page).toHaveTitle('Gel | Integrations 📚 Qwik Documentation');
});

test('Integrations Internationalization page loads', async ({ page }) => {
await page.goto('/docs/integrations/i18n/');
await expect(page).toHaveTitle('Internationalization | Integrations 📚 Qwik Documentation');
Expand Down
1 change: 1 addition & 0 deletions e2e/docs-e2e/tests/Docs/pages-load-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ test('docs page loads', async ({ page }) => {
'Builder.io',
'Cypress',
'Drizzle',
'Gel',
'i18n',
'Icons',
'Image Optimization',
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,13 @@
"serve.debug": "tsx --require ./scripts/runBefore.ts --inspect-brk --conditions=development starters/dev-server.ts 3300",
"start": "pnpm run --stream \"/.*\\.watch/\"",
"test": "pnpm build.full && pnpm test.unit && pnpm test.e2e",
"test.e2e": "pnpm test.e2e.chromium && pnpm test.e2e.webkit && test.e2e.integrations",
"test.e2e": "pnpm test.e2e.chromium && pnpm test.e2e.webkit && pnpm test.e2e.integrations",
"test.e2e.chromium": "playwright test starters --browser=chromium --config starters/playwright.config.ts",
"test.e2e.chromium.debug": "PWDEBUG=1 playwright test starters --browser=chromium --config starters/playwright.config.ts",
"test.e2e.city": "playwright test starters/e2e/qwikcity --browser=chromium --config starters/playwright.config.ts",
"test.e2e.cli": "pnpm --filter qwik-cli-e2e e2e",
"test.e2e.firefox": "playwright test starters --browser=firefox --config starters/playwright.config.ts",
"test.e2e.integrations": "pnpm test.e2e.integrations.chromium && pnpm test.e2e.integrations.webkit",
"test.e2e.integrations.chromium": "playwright test e2e/adapters-e2e/tests --project=chromium --config e2e/adapters-e2e/playwright.config.ts",
"test.e2e.integrations.webkit": "playwright test e2e/adapters-e2e/tests --project=webkit --config e2e/adapters-e2e/playwright.config.ts",
"test.e2e.webkit": "playwright test starters --browser=webkit --config starters/playwright.config.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -1774,7 +1774,7 @@
}
],
"kind": "Function",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[JSXNode](#jsxnode)<!-- -->&lt;'script'&gt;",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nJSXNode&lt;'script'&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts",
"mdFile": "qwik.prefetchserviceworker.md"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/routes/api/qwik/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3667,7 +3667,7 @@ opts

**Returns:**

[JSXNode](#jsxnode)&lt;'script'&gt;
JSXNode&lt;'script'&gt;

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts)

Expand Down
62 changes: 62 additions & 0 deletions packages/docs/src/routes/docs/integrations/gel/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: Gel | Integrations
keywords: 'Gel, EdgeDB, database, data, postgres'
contributors:
- nabrams
updated_at: '2025-09-25T18:53:23Z'
created_at: '2025-08-01T23:00:50Z'
---

import PackageManagerTabs from '~/components/package-manager-tabs/index.tsx';

# Gel Data

[GelDB](https://www.geldata.com/) Gel is a scalable, integrated data platform on top of Postgres.

Gel gives the relational model a fresh facelift, solves n+1, simplifies migrations,

and streamlines your entire database workflow.

Gel can be used in Qwik with `routeLoader$`, `routeAction$` and `server$` functions. These are Qwik APIs to allow code to execute only on the server-side.

The easiest way to add Gel to Qwik is using the Qwik CLI command. This will install the required dependencies and create a `gel` folder with the gel schema.

It will also create a `gel.toml` file to configure the gel instance. Everything you need to get started is there.

<PackageManagerTabs>
<span q:slot="pnpm">
```shell
pnpm run qwik add gel
```
</span>
<span q:slot="npm">
```shell
npm run qwik add gel
```
</span>
<span q:slot="yarn">
```shell
yarn run qwik add gel
```
</span>
<span q:slot="bun">
```shell
bun run qwik add gel
```
</span>
</PackageManagerTabs>

> Gel is a modern data platform built on PostgreSQL that provides a unified interface for managing, querying, and scaling your data. It offers features like real-time analytics, seamless migrations, and integrated workflows to simplify database operations for developers and teams.

## Auto-generating queries

Gel lets you automatically generate queries for your database schema by using

their query language EdgeQL combined with `npm install @gel/generate --save-dev`.

The following `<generator>`s are currently supported:

- `queries`: Generate typed functions from *.edgeql files
- `interfaces`: Generate interfaces for your schema types
- `edgeql-js`: Generate the query builder

1 change: 1 addition & 0 deletions packages/docs/src/routes/docs/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
- [Builder.io](integrations/builderio/index.mdx)
- [Cypress](integrations/cypress/index.mdx)
- [Drizzle](integrations/drizzle/index.mdx)
- [Gel](integrations/gel/index.mdx)
- [i18n](integrations/i18n/index.mdx)
- [Icons](integrations/icons/index.mdx)
- [Image Optimization](integrations/image-optimization/index.mdx)
Expand Down
28 changes: 0 additions & 28 deletions packages/qwik/src/optimizer/src/qwik-binding-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,6 @@ export const QWIK_BINDING_MAP = {
"abi": null,
"platformArchABI": "qwik.darwin-arm64.node"
}
],
"x64": [
{
"platform": "darwin",
"arch": "x64",
"abi": null,
"platformArchABI": "qwik.darwin-x64.node"
}
]
},
"win32": {
"x64": [
{
"platform": "win32",
"arch": "x64",
"abi": "msvc",
"platformArchABI": "qwik.win32-x64-msvc.node"
}
]
},
"linux": {
"x64": [
{
"platform": "linux",
"arch": "x64",
"abi": "gnu",
"platformArchABI": "qwik.linux-x64-gnu.node"
}
]
}
};
1 change: 0 additions & 1 deletion packages/qwik/src/qwikloader.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ test('qwikloader script', () => {
`);

expect(qwikLoader).toMatchInlineSnapshot(
//eslint-disable-next-line
`"const t=document,e=window,n=new Set,o=new Set([t]);let r;const s=(t,e)=>Array.from(t.querySelectorAll(e)),a=t=>{const e=[];return o.forEach(n=>e.push(...s(n,t))),e},i=t=>{w(t),s(t,"[q\\\\:shadowroot]").forEach(t=>{const e=t.shadowRoot;e&&i(e)})},c=t=>t&&"function"==typeof t.then,l=(t,e,n=e.type)=>{a("[on"+t+"\\\\:"+n+"]").forEach(o=>{b(o,t,e,n)})},f=e=>{if(void 0===e._qwikjson_){let n=(e===t.documentElement?t.body:e).lastElementChild;for(;n;){if("SCRIPT"===n.tagName&&"qwik/json"===n.getAttribute("type")){e._qwikjson_=JSON.parse(n.textContent.replace(/\\\\x3C(\\/?script)/gi,"<$1"));break}n=n.previousElementSibling}}},p=(t,e)=>new CustomEvent(t,{detail:e}),b=async(e,n,o,r=o.type)=>{const s="on"+n+":"+r;e.hasAttribute("preventdefault:"+r)&&o.preventDefault(),e.hasAttribute("stoppropagation:"+r)&&o.stopPropagation();const a=e._qc_,i=a&&a.li.filter(t=>t[0]===s);if(i&&i.length>0){for(const t of i){const n=t[1].getFn([e,o],()=>e.isConnected)(o,e),r=o.cancelBubble;c(n)&&await n,r&&o.stopPropagation()}return}const l=e.getAttribute(s);if(l){const n=e.closest("[q\\\\:container]"),r=n.getAttribute("q:base"),s=n.getAttribute("q:version")||"unknown",a=n.getAttribute("q:manifest-hash")||"dev",i=new URL(r,t.baseURI);for(const p of l.split("\\n")){const l=new URL(p,i),b=l.href,h=l.hash.replace(/^#?([^?[|]*).*$/,"$1")||"default",q=performance.now();let _,d,y;const w=p.startsWith("#"),g={qBase:r,qManifest:a,qVersion:s,href:b,symbol:h,element:e,reqTime:q};if(w){const e=n.getAttribute("q:instance");_=(t["qFuncs_"+e]||[])[Number.parseInt(h)],_||(d="sync",y=Error("sym:"+h))}else{u("qsymbol",g);const t=l.href.split("#")[0];try{const e=import(t);f(n),_=(await e)[h],_||(d="no-symbol",y=Error(\`\${h} not in \${t}\`))}catch(t){d||(d="async"),y=t}}if(!_){u("qerror",{importError:d,error:y,...g}),console.error(y);break}const m=t.__q_context__;if(e.isConnected)try{t.__q_context__=[e,o,l];const n=_(o,e);c(n)&&await n}catch(t){u("qerror",{error:t,...g})}finally{t.__q_context__=m}}}},u=(e,n)=>{t.dispatchEvent(p(e,n))},h=t=>t.replace(/([A-Z])/g,t=>"-"+t.toLowerCase()),q=async t=>{let e=h(t.type),n=t.target;for(l("-document",t,e);n&&n.getAttribute;){const o=b(n,"",t,e);let r=t.cancelBubble;c(o)&&await o,r||(r=r||t.cancelBubble||n.hasAttribute("stoppropagation:"+t.type)),n=t.bubbles&&!0!==r?n.parentElement:null}},_=t=>{l("-window",t,h(t.type))},d=()=>{const s=t.readyState;if(!r&&("interactive"==s||"complete"==s)&&(o.forEach(i),r=1,u("qinit"),(e.requestIdleCallback??e.setTimeout).bind(e)(()=>u("qidle")),n.has("qvisible"))){const t=a("[on\\\\:qvisible]"),e=new IntersectionObserver(t=>{for(const n of t)n.isIntersecting&&(e.unobserve(n.target),b(n.target,"",p("qvisible",n)))});t.forEach(t=>e.observe(t))}},y=(t,e,n,o=!1)=>{t.addEventListener(e,n,{capture:o,passive:!1})},w=(...t)=>{for(const r of t)"string"==typeof r?n.has(r)||(o.forEach(t=>y(t,r,q,!0)),y(e,r,_,!0),n.add(r)):o.has(r)||(n.forEach(t=>y(r,t,q,!0)),o.add(r))};if(!("__q_context__"in t)){t.__q_context__=0;const r=e.qwikevents;r&&(Array.isArray(r)?w(...r):w("click","input")),e.qwikevents={events:n,roots:o,push:w},y(t,"readystatechange",d),d()}"`
);
});
3 changes: 2 additions & 1 deletion starters/e2e/e2e.use-id.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect, Locator } from "@playwright/test";
import { test, expect } from "@playwright/test";
import type { Locator } from "@playwright/test";

test.describe("use-id", () => {
test.beforeEach(async ({ page }) => {
Expand Down
29 changes: 29 additions & 0 deletions starters/features/gel/dbschema/default.gel
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module default {
abstract type Timestamped {
last_updated: datetime {
rewrite insert, update using (datetime_of_statement());
};
}

type User extending Timestamped{
errmessage := 'A user with that name already exists!';
required name: str {
constraint exclusive;
};
description: str;
has_profile: bool;
multi emails: Email;

index on (.name);

}

type Email {
errmessage := 'an email with that address already exists!';
required address: str {
constraint exclusive;
};
provider: str;
required user: User;
}
}
33 changes: 33 additions & 0 deletions starters/features/gel/dbschema/extensions.gel
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This file contains Gel extensions used by the project.
# Uncomment the `using extension ...` below to enable them.


# Gel Auth is a batteries-included authentication solution
# for your app built into the Gel server.
#
# See: https://docs.geldata.com/reference/auth
#
#using extension auth;


# Gel AI is a set of tools designed to enable you to ship
# AI-enabled apps with practically no effort.
#
# See: https://docs.geldata.com/reference/ai
#
#using extension ai;


# The `ext::postgis` extension exposes the functionality of the
# PostGIS library. It is a vast library dedicated to handling
# geographic and various geometric data. The scope of the Gel
# extension is to mainly adapt the types and functions used in
# this library with minimal changes.
#
# See: https://docs.geldata.com/reference/stdlib/postgis
#
# `ext::postgis` is not installed by default, use the command
# `gel extension` to manage its installation, then uncomment
# the line below to enable it.
#
#using extension postgis;
4 changes: 4 additions & 0 deletions starters/features/gel/dbschema/futures.gel
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Use a simpler algorithm for resolving the scope of object names.
# This behavior will become the default in Gel 8.0.
# See: https://docs.geldata.com/reference/edgeql/path_resolution#new-path-scoping
using future simple_scoping;
35 changes: 35 additions & 0 deletions starters/features/gel/dbschema/migrations/00001-m1nmosd.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
CREATE MIGRATION m1nmosduidfn2cl6attui644sivfoenkklq3y6m2b6vacma2pwoeva
ONTO initial
{
CREATE FUTURE simple_scoping;
CREATE TYPE default::Email {
CREATE REQUIRED PROPERTY address: std::str {
CREATE CONSTRAINT std::exclusive;
};
CREATE PROPERTY errmessage := ('an email with that address already exists!');
CREATE PROPERTY provider: std::str;
};
CREATE ABSTRACT TYPE default::Timestamped {
CREATE PROPERTY last_updated: std::datetime {
CREATE REWRITE
INSERT
USING (std::datetime_of_statement());
CREATE REWRITE
UPDATE
USING (std::datetime_of_statement());
};
};
CREATE TYPE default::User EXTENDING default::Timestamped {
CREATE MULTI LINK emails: default::Email;
CREATE REQUIRED PROPERTY name: std::str {
CREATE CONSTRAINT std::exclusive;
};
CREATE INDEX ON (.name);
CREATE PROPERTY description: std::str;
CREATE PROPERTY errmessage := ('A user with that name already exists!');
CREATE PROPERTY has_profile: std::bool;
};
ALTER TYPE default::Email {
CREATE REQUIRED LINK user: default::User;
};
};
7 changes: 7 additions & 0 deletions starters/features/gel/gel.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
watch = []

[instance]
server-version = "6.7"

[hooks]
schema.update.after = "pnpm generate queries --file"
39 changes: 39 additions & 0 deletions starters/features/gel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"description": "Gel: Graph Relational Database on top of Postgres",
"__qwik__": {
"displayName": "Integration: GelDB",
"priority": -10,
"docs": [
"https://www.geldata.com/docs/quickstart"
],
"viteConfig": {},
"nextSteps": {
"lines": [
" Gel was installed with a simple DB schema and some demo routes,",
"",
"",
"",
" Run `npm run gel:ui` to access the Gel UI and see the database schema.",
"Other useful commands are:",
" - `npm run gel:migrate` to create and run migrations",
" - `npm run gel:generate:all` to generate queries, types, and query builders",
" - `npm run gel:generate:queries` to generate queries",
" - `npm run gel:generate:types` to generate types and query builders",
"",
" Check out the Gel docs for more info:",
" - https://www.geldata.com/docs/overview"
]
}
},
"scripts": {
"gel:migrate": "gel migration create && gel migrate ",
"gel:generate:queries": "generate queries && generate queries --file",
"gel:generate:types": "generate interfaces && generate edgeql-js",
"gel:generate:all": "gel:generate:queries && gel:generate:types",
"gel:ui": "gel ui"
},
"dependencies": {
"@gel/generate": "^0.6.4",
"gel": "^2.1.1"
}
}
Empty file.
5 changes: 5 additions & 0 deletions starters/features/gel/queries/getUser.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
select User { **
} filter assert_exists(User.name) ?= <optional str>$name;

# Filter here is optional.
# If no filter is provided, all Users will be returned.
27 changes: 27 additions & 0 deletions starters/features/gel/queries/insertOrUpdateEmail.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
with
NewEmail := (
insert Email {
address := <str>$address,
provider := <str>$package_version,
user := (
select User
filter .name = <str>$name
limit 1
)
}
unless conflict on .address
else (
update Email
filter .address = <str>$address
set {
provider := <str>$package_version,
user := (
select User
filter .name = <str>$name
limit 1
)
}
)
)

select NewEmail { ** };
Loading