diff --git a/e2e/docs-e2e/tests/Docs/integrations-pages-load.spec.ts b/e2e/docs-e2e/tests/Docs/integrations-pages-load.spec.ts
index d580c8457b6..aa97902362a 100644
--- a/e2e/docs-e2e/tests/Docs/integrations-pages-load.spec.ts
+++ b/e2e/docs-e2e/tests/Docs/integrations-pages-load.spec.ts
@@ -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');
diff --git a/e2e/docs-e2e/tests/Docs/pages-load-test.spec.ts b/e2e/docs-e2e/tests/Docs/pages-load-test.spec.ts
index 1720624cc71..d6b4b3b559f 100644
--- a/e2e/docs-e2e/tests/Docs/pages-load-test.spec.ts
+++ b/e2e/docs-e2e/tests/Docs/pages-load-test.spec.ts
@@ -102,6 +102,7 @@ test('docs page loads', async ({ page }) => {
'Builder.io',
'Cypress',
'Drizzle',
+ 'Gel',
'i18n',
'Icons',
'Image Optimization',
diff --git a/package.json b/package.json
index 3410b15ea69..8f66b16e139 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json
index 91d5fcdccaa..8750f3fe20d 100644
--- a/packages/docs/src/routes/api/qwik/api.json
+++ b/packages/docs/src/routes/api/qwik/api.json
@@ -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
\n\nParameter\n\n\n | \n\nType\n\n\n | \n\nDescription\n\n\n |
\n\n\nopts\n\n\n | \n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n | \n\n\n |
\n
\n\n**Returns:**\n\n[JSXNode](#jsxnode)<'script'>",
+ "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\n\nParameter\n\n\n | \n\nType\n\n\n | \n\nDescription\n\n\n |
\n\n\nopts\n\n\n | \n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n | \n\n\n |
\n
\n\n**Returns:**\n\nJSXNode<'script'>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts",
"mdFile": "qwik.prefetchserviceworker.md"
},
diff --git a/packages/docs/src/routes/api/qwik/index.mdx b/packages/docs/src/routes/api/qwik/index.mdx
index 1ddbfe7a032..0b47efdd79d 100644
--- a/packages/docs/src/routes/api/qwik/index.mdx
+++ b/packages/docs/src/routes/api/qwik/index.mdx
@@ -3667,7 +3667,7 @@ opts
**Returns:**
-[JSXNode](#jsxnode)<'script'>
+JSXNode<'script'>
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts)
diff --git a/packages/docs/src/routes/docs/integrations/gel/index.mdx b/packages/docs/src/routes/docs/integrations/gel/index.mdx
new file mode 100644
index 00000000000..2d4fd80d4b8
--- /dev/null
+++ b/packages/docs/src/routes/docs/integrations/gel/index.mdx
@@ -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.
+
+
+
+```shell
+pnpm run qwik add gel
+```
+
+
+```shell
+npm run qwik add gel
+```
+
+
+```shell
+yarn run qwik add gel
+```
+
+
+```shell
+bun run qwik add gel
+```
+
+
+
+> 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 ``s are currently supported:
+
+- `queries`: Generate typed functions from *.edgeql files
+- `interfaces`: Generate interfaces for your schema types
+- `edgeql-js`: Generate the query builder
+
diff --git a/packages/docs/src/routes/docs/menu.md b/packages/docs/src/routes/docs/menu.md
index d77d6583a3f..bfd5fda55ee 100644
--- a/packages/docs/src/routes/docs/menu.md
+++ b/packages/docs/src/routes/docs/menu.md
@@ -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)
diff --git a/packages/qwik/src/optimizer/src/qwik-binding-map.ts b/packages/qwik/src/optimizer/src/qwik-binding-map.ts
index cceeb273668..857c9ae8fff 100644
--- a/packages/qwik/src/optimizer/src/qwik-binding-map.ts
+++ b/packages/qwik/src/optimizer/src/qwik-binding-map.ts
@@ -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"
- }
]
}
};
diff --git a/packages/qwik/src/qwikloader.unit.ts b/packages/qwik/src/qwikloader.unit.ts
index 08dede8f7d3..2e201d2a0cf 100644
--- a/packages/qwik/src/qwikloader.unit.ts
+++ b/packages/qwik/src/qwikloader.unit.ts
@@ -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()}"`
);
});
diff --git a/starters/e2e/e2e.use-id.spec.ts b/starters/e2e/e2e.use-id.spec.ts
index d98e732b749..e2ee7279229 100644
--- a/starters/e2e/e2e.use-id.spec.ts
+++ b/starters/e2e/e2e.use-id.spec.ts
@@ -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 }) => {
diff --git a/starters/features/gel/dbschema/default.gel b/starters/features/gel/dbschema/default.gel
new file mode 100644
index 00000000000..e4bedd9e379
--- /dev/null
+++ b/starters/features/gel/dbschema/default.gel
@@ -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;
+ }
+}
diff --git a/starters/features/gel/dbschema/extensions.gel b/starters/features/gel/dbschema/extensions.gel
new file mode 100644
index 00000000000..c566b414fd4
--- /dev/null
+++ b/starters/features/gel/dbschema/extensions.gel
@@ -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;
diff --git a/starters/features/gel/dbschema/futures.gel b/starters/features/gel/dbschema/futures.gel
new file mode 100644
index 00000000000..20f2ff302b4
--- /dev/null
+++ b/starters/features/gel/dbschema/futures.gel
@@ -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;
diff --git a/starters/features/gel/dbschema/migrations/00001-m1nmosd.edgeql b/starters/features/gel/dbschema/migrations/00001-m1nmosd.edgeql
new file mode 100644
index 00000000000..4da7c1fb98e
--- /dev/null
+++ b/starters/features/gel/dbschema/migrations/00001-m1nmosd.edgeql
@@ -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;
+ };
+};
diff --git a/starters/features/gel/gel.toml b/starters/features/gel/gel.toml
new file mode 100644
index 00000000000..e2752ceea62
--- /dev/null
+++ b/starters/features/gel/gel.toml
@@ -0,0 +1,7 @@
+watch = []
+
+[instance]
+server-version = "6.7"
+
+[hooks]
+schema.update.after = "pnpm generate queries --file"
\ No newline at end of file
diff --git a/starters/features/gel/package.json b/starters/features/gel/package.json
new file mode 100644
index 00000000000..16bfa82a4cd
--- /dev/null
+++ b/starters/features/gel/package.json
@@ -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"
+ }
+}
diff --git a/starters/features/gel/queries/.gitkeep b/starters/features/gel/queries/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/starters/features/gel/queries/getUser.edgeql b/starters/features/gel/queries/getUser.edgeql
new file mode 100644
index 00000000000..b47fc5bee06
--- /dev/null
+++ b/starters/features/gel/queries/getUser.edgeql
@@ -0,0 +1,5 @@
+select User { **
+} filter assert_exists(User.name) ?= $name;
+
+# Filter here is optional.
+# If no filter is provided, all Users will be returned.
diff --git a/starters/features/gel/queries/insertOrUpdateEmail.edgeql b/starters/features/gel/queries/insertOrUpdateEmail.edgeql
new file mode 100644
index 00000000000..d7eab2683ef
--- /dev/null
+++ b/starters/features/gel/queries/insertOrUpdateEmail.edgeql
@@ -0,0 +1,27 @@
+with
+ NewEmail := (
+ insert Email {
+ address := $address,
+ provider := $package_version,
+ user := (
+ select User
+ filter .name = $name
+ limit 1
+ )
+ }
+ unless conflict on .address
+ else (
+ update Email
+ filter .address = $address
+ set {
+ provider := $package_version,
+ user := (
+ select User
+ filter .name = $name
+ limit 1
+ )
+ }
+ )
+ )
+
+select NewEmail { ** };
diff --git a/starters/features/gel/queries/insertOrUpdateUser.edgeql b/starters/features/gel/queries/insertOrUpdateUser.edgeql
new file mode 100644
index 00000000000..ab499c56e3e
--- /dev/null
+++ b/starters/features/gel/queries/insertOrUpdateUser.edgeql
@@ -0,0 +1,39 @@
+with
+ NewUser := (
+ insert User {
+ name := $name,
+ description := $package_version,
+ has_profile := $has_profile,
+ }
+ unless conflict on .name
+ else (
+ update User
+ filter .name = $name
+ set {
+ package_version := $package_version,
+ description := $package_version,
+ has_profile := $has_profile,
+ }
+ )
+ ),
+
+ InsertEmails := (
+ for email in array_unpack(>>$dependencies)
+ union (
+ insert Email {
+ address := email.address,
+ provider := email.provider,
+ user := NewUser,
+ }
+ unless conflict on (.address)
+ else (
+ update Email
+ filter .address = email.address
+ set {
+ provider := email.provider,
+ }
+ )
+ )
+ ),
+
+select NewUser { ** };
diff --git a/starters/features/gel/src/.gitkeep b/starters/features/gel/src/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/starters/features/gel/src/actions/.gitkeep b/starters/features/gel/src/actions/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/starters/features/gel/src/actions/client.ts b/starters/features/gel/src/actions/client.ts
new file mode 100644
index 00000000000..2e63a8f4556
--- /dev/null
+++ b/starters/features/gel/src/actions/client.ts
@@ -0,0 +1,75 @@
+import { createClient, type Executor } from "gel";
+
+// Private state via closure
+let client: Executor | null = null;
+
+const isConnectionError = (error: unknown): boolean => {
+ const message = error instanceof Error ? error.message.toLowerCase() : "";
+ return (
+ message.includes("connection") ||
+ message.includes("timeout") ||
+ message.includes("network") ||
+ message.includes("invalidreferenceerror")
+ );
+};
+
+const createNewClient = (): Executor => {
+ console.log("Creating new database client");
+ return createClient();
+};
+
+const closeClient = (): void => {
+ if (client) {
+ client = null;
+ console.log("Database client closed");
+ }
+};
+
+// Main client management functions
+export const getClient = (): Executor => {
+ if (!client) {
+ closeClient();
+ client = createNewClient();
+ }
+
+ return client;
+};
+
+// Regular async function preserves generic types with optional args
+export const executeQuery = async (
+ queryFn: TArgs extends void
+ ? (client: Executor) => Promise
+ : (client: Executor, args: TArgs) => Promise,
+ args?: TArgs,
+): Promise => {
+ const dbClient = getClient();
+
+ try {
+ if (args === undefined) {
+ return await (queryFn as (client: Executor) => Promise)(dbClient);
+ } else {
+ return await (queryFn as (client: Executor, args: TArgs) => Promise)(
+ dbClient,
+ args,
+ );
+ }
+ } catch (error) {
+ console.error("Database query failed:", error);
+
+ if (isConnectionError(error)) {
+ console.log("Connection error detected, recreating client...");
+ closeClient();
+ const newClient = getClient();
+ if (args === undefined) {
+ return await (queryFn as (client: Executor) => Promise)(newClient);
+ } else {
+ return await (queryFn as (client: Executor, args: TArgs) => Promise)(
+ newClient,
+ args,
+ );
+ }
+ }
+
+ throw error;
+ }
+};
diff --git a/starters/features/gel/src/actions/user/.gitkeep b/starters/features/gel/src/actions/user/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/starters/features/gel/src/actions/user/index.ts b/starters/features/gel/src/actions/user/index.ts
new file mode 100644
index 00000000000..450db747a5c
--- /dev/null
+++ b/starters/features/gel/src/actions/user/index.ts
@@ -0,0 +1,7 @@
+import { server$ } from "@qwik.dev/router";
+import { executeQuery } from "../client";
+import * as queries from "../../../gel/queries";
+
+export const getAllUsers = server$(async () => {
+ return await executeQuery(queries.getAllUsers);
+});
diff --git a/starters/features/gel/src/hooks/.gitkeep b/starters/features/gel/src/hooks/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/starters/features/gel/src/hooks/user.hooks.ts b/starters/features/gel/src/hooks/user.hooks.ts
new file mode 100644
index 00000000000..4b92c1a0524
--- /dev/null
+++ b/starters/features/gel/src/hooks/user.hooks.ts
@@ -0,0 +1,11 @@
+import { useSignal, useTask$ } from "@builder.io/qwik";
+import { getAllUsers } from "../actions/user";
+
+export const useGetUsers = () => {
+ const signal = useSignal>>([]);
+ useTask$(async () => {
+ const result = await getAllUsers();
+ signal.value = result;
+ });
+ return signal;
+};
diff --git a/starters/features/tailwind-v3/prettier.config.js b/starters/features/tailwind-v3/prettier.config.mjs
similarity index 100%
rename from starters/features/tailwind-v3/prettier.config.js
rename to starters/features/tailwind-v3/prettier.config.mjs