@qelos/integrator-next 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Polyform Shield License 1.0.0
2
+
3
+ Copyright (c) 2025 Velocitech LTD
4
+
5
+ Your use of this software is governed by the Polyform Shield License 1.0.0.
6
+
7
+ You may obtain a copy of the License at:
8
+ https://polyformproject.org/licenses/shield/1.0.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+
12
+ Additional Terms – Exception Notice:
13
+
14
+ The author of this software grants additional permissions beyond the Polyform Shield License 1.0.0:
15
+
16
+ ✅ All uses of the software are permitted — including modification, distribution, commercial use, and sublicensing — as long as the software is not used to create, offer, or operate any product or service that competes directly with Qelos (https://qelos.io), a no-code platform for building AI-assisted SaaS applications.
17
+
18
+ ❌ "Direct competition" includes platforms, tools, or services that enable users to visually or programmatically create SaaS applications with the assistance of artificial intelligence.
19
+
20
+ This exception does not modify the terms of the original Polyform Shield License for other users and applies only by explicit permission of the licensor.
package/README.md ADDED
@@ -0,0 +1,256 @@
1
+ # @qelos/integrator-next
2
+
3
+ Qelos integrator middleware for **Next.js** — supports both the **App Router**
4
+ and the **Pages Router**, on Next.js **14** and **15**. Drop it into your
5
+ Next.js app to identify the user and active workspace via the Qelos SDK
6
+ *before* your own handlers run, and to keep refresh-token cookies fresh on
7
+ every request.
8
+
9
+ The package is a sibling of `@qelos/integrator-express` and
10
+ `@qelos/integrator-nuxt` — they all expose the same `QelosRequestContext`
11
+ shape (`{ user, workspace, workspaces, sdk, tokens }`).
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pnpm add @qelos/integrator-next @qelos/sdk
17
+ ```
18
+
19
+ `next` (>=13.4) and `react` are peer dependencies. Tested with Next.js 14.2
20
+ and 15.x on both the App Router and the Pages Router.
21
+
22
+ ## Quick start
23
+
24
+ Set `QELOS_APP_URL` (and any optional vars below) in your environment, then
25
+ re-export the pre-built middleware and use the no-arg context loader:
26
+
27
+ ```ts
28
+ // middleware.ts
29
+ export { qelosMiddleware as middleware } from '@qelos/integrator-next/middleware';
30
+
31
+ export const config = {
32
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
33
+ };
34
+ ```
35
+
36
+ ```tsx
37
+ // app/dashboard/page.tsx
38
+ import { getQelosContext } from '@qelos/integrator-next/context';
39
+
40
+ export default async function Dashboard() {
41
+ const { user, sdk } = await getQelosContext();
42
+ if (!user) return <p>Please sign in.</p>;
43
+ const products = await sdk.entities('products').getList();
44
+ return <div>Welcome {user.fullName}, you have {products.length} products</div>;
45
+ }
46
+ ```
47
+
48
+ ### Environment variables
49
+
50
+ | Variable | Default | Description |
51
+ |---|---|---|
52
+ | `QELOS_APP_URL` | — (required) | Qelos backend base URL |
53
+ | `QELOS_API_TOKEN` | — | Service-to-service token (skips refresh logic) |
54
+ | `QELOS_ACCESS_TOKEN_COOKIE` | `q_access_token` | Cookie name for the access token |
55
+ | `QELOS_REFRESH_TOKEN_COOKIE` | `q_refresh_token` | Cookie name for the refresh token |
56
+ | `QELOS_REQUIRE_AUTH` | `false` | Set `true` to reject anonymous requests with 401 |
57
+ | `QELOS_SKIP_PATHS` | — | Comma-separated path prefixes to bypass entirely |
58
+
59
+ ## Explicit configuration (App Router)
60
+
61
+ When you need control over `resolveWorkspace`, `onTokenRefresh`, or any other
62
+ option, call the factory yourself.
63
+
64
+ ### 1. Edge middleware
65
+
66
+ ```ts
67
+ // middleware.ts
68
+ import { createQelosMiddleware } from '@qelos/integrator-next/middleware';
69
+
70
+ export default createQelosMiddleware({
71
+ config: {
72
+ appUrl: process.env.QELOS_APP_URL!,
73
+ skipPaths: ['/_next', '/favicon.ico'],
74
+ requireAuth: false,
75
+ },
76
+ resolveWorkspace: ({ req, workspaces }) => {
77
+ const slug = req.nextUrl.searchParams.get('workspace');
78
+ return workspaces.find((w) => (w as any).slug === slug) ?? workspaces[0] ?? null;
79
+ },
80
+ });
81
+
82
+ export const config = {
83
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
84
+ };
85
+ ```
86
+
87
+ The middleware:
88
+
89
+ - Reads tokens from cookies (`q_access_token`, `q_refresh_token`) or the
90
+ `Authorization` header.
91
+ - Calls the Qelos SDK to resolve the user and the active workspace.
92
+ - Forwards the resolved ids to downstream handlers via the
93
+ `x-qelos-user-id` and `x-qelos-workspace-id` request headers.
94
+ - Refreshes the access/refresh token pair when needed and writes the new
95
+ cookies onto the outbound response.
96
+ - Returns `401` early when `config.requireAuth: true` and the user cannot be
97
+ resolved.
98
+
99
+ ### 2. Server components (RSC)
100
+
101
+ `getQelosContext()` is safe to call inside React Server Components on both
102
+ Next.js 14 (sync `cookies()` / `headers()`) and Next.js 15 (async). Wrap it
103
+ with React's `cache()` so re-renders within the same request reuse the result:
104
+
105
+ ```tsx
106
+ // lib/qelos.ts
107
+ import { cache } from 'react';
108
+ import { getQelosContext } from '@qelos/integrator-next/context';
109
+
110
+ export const loadQelos = cache(() => getQelosContext());
111
+ ```
112
+
113
+ ```tsx
114
+ // app/page.tsx
115
+ import { loadQelos } from '../lib/qelos';
116
+
117
+ export default async function Page() {
118
+ const { user, workspace } = await loadQelos();
119
+ return <p>Hello {user?.fullName ?? 'guest'} — {workspace?.name}</p>;
120
+ }
121
+ ```
122
+
123
+ ### 3. Route handlers (`app/api/**/route.ts`)
124
+
125
+ ```ts
126
+ // app/api/me/route.ts
127
+ import { NextResponse } from 'next/server';
128
+ import { withQelosRoute } from '@qelos/integrator-next/route';
129
+
130
+ export const GET = withQelosRoute((_req, _ctx, qelos) => {
131
+ if (!qelos.user) return NextResponse.json({ user: null });
132
+ return NextResponse.json({ user: qelos.user, workspace: qelos.workspace });
133
+ });
134
+ ```
135
+
136
+ Anywhere downstream — including modules outside the React tree — can read the
137
+ active context from AsyncLocalStorage via `getStoredQelosContext()`, provided
138
+ execution went through `withQelosRoute`, `withQelosContext`, or one of the
139
+ Pages Router wrappers.
140
+
141
+ ```ts
142
+ // services/products.ts
143
+ import { getStoredQelosContext } from '@qelos/integrator-next/context';
144
+
145
+ export async function listProducts() {
146
+ const qelos = getStoredQelosContext();
147
+ if (!qelos?.sdk) throw new Error('no qelos context');
148
+ return qelos.sdk.entities('products').getList();
149
+ }
150
+ ```
151
+
152
+ ## Pages Router
153
+
154
+ ### API routes
155
+
156
+ ```ts
157
+ // pages/api/me.ts
158
+ import { withQelosApi } from '@qelos/integrator-next/pages';
159
+
160
+ export default withQelosApi(
161
+ (req, res) => {
162
+ res.json({ user: req.qelos!.user, workspace: req.qelos!.workspace });
163
+ },
164
+ { config: { appUrl: process.env.QELOS_APP_URL! } },
165
+ );
166
+ ```
167
+
168
+ ### `getServerSideProps`
169
+
170
+ ```ts
171
+ // pages/dashboard.tsx
172
+ import { withQelosSSR } from '@qelos/integrator-next/pages';
173
+
174
+ export const getServerSideProps = withQelosSSR(
175
+ async (_ctx, qelos) => ({ props: { user: qelos.user, workspace: qelos.workspace } }),
176
+ { config: { appUrl: process.env.QELOS_APP_URL!, requireAuth: true } },
177
+ );
178
+ ```
179
+
180
+ ## Token refresh
181
+
182
+ When a request arrives with an expired access token, the SDK's failed-auth
183
+ path triggers a refresh, in order:
184
+
185
+ 1. If a refresh token is present, calls
186
+ `sdk.authentication.refreshToken()` (`POST /api/token/refresh`) — issues a
187
+ new access + refresh pair.
188
+ 2. Otherwise, calls `sdk.authentication.refreshCookieToken()`
189
+ (`POST /api/cookie/refresh`) — used for cookie-only sessions that do not
190
+ carry a separate refresh token (e.g. social-auth flows).
191
+
192
+ The new pair is then written back to cookies using the configured
193
+ `accessTokenCookie` / `refreshTokenCookie` names — automatically in App
194
+ Router middleware (`response.cookies.set`), Pages Router API routes
195
+ (`Set-Cookie` header), and `getServerSideProps` responses.
196
+
197
+ App Router server components and route handlers cannot mutate cookies after
198
+ rendering starts, so the default refresh hook there is a no-op — the new
199
+ tokens are still applied to the in-memory pair so subsequent SDK calls in the
200
+ same request use the fresh access token. To persist the refreshed pair, hand
201
+ the request off to a route handler that calls `cookies().set(...)`.
202
+
203
+ Override the behaviour by passing `onTokenRefresh` to any of the wrappers.
204
+ The hook receives:
205
+
206
+ ```ts
207
+ interface TokenRefreshContext<T> {
208
+ target: T; // NextResponse | NextApiResponse | ServerResponse | null
209
+ oldTokens: { accessToken?: string; refreshToken?: string };
210
+ newTokens: { accessToken: string; refreshToken?: string };
211
+ sdk: QelosSDK;
212
+ }
213
+ ```
214
+
215
+ ### Manual cookie refresh
216
+
217
+ Long-lived integrator-hosted sessions can also call the SDK directly to
218
+ proactively refresh the cookie token. From a route handler that owns the
219
+ response (so it can mutate cookies):
220
+
221
+ ```ts
222
+ // app/api/session/refresh/route.ts
223
+ import { NextResponse } from 'next/server';
224
+ import { withQelosRoute } from '@qelos/integrator-next/route';
225
+
226
+ export const POST = withQelosRoute(async (_req, _ctx, qelos) => {
227
+ const result = await qelos.sdk.authentication.refreshCookieToken();
228
+ // result.headers['set-cookie'] — fresh cookie value to forward
229
+ return NextResponse.json({ user: result.payload.user });
230
+ });
231
+ ```
232
+
233
+ ## Configuration
234
+
235
+ ```ts
236
+ interface QelosNextConfig {
237
+ appUrl: string; // Qelos backend base URL
238
+ apiToken?: string; // service-to-service token (skips refresh logic)
239
+ accessTokenCookie?: string; // default 'q_access_token'
240
+ refreshTokenCookie?: string; // default 'q_refresh_token'
241
+ requireAuth?: boolean; // 401/redirect on anonymous (default false)
242
+ skipPaths?: string[]; // path prefixes to bypass entirely
243
+ sdkOptions?: Partial<QelosSDKOptions>;
244
+ }
245
+ ```
246
+
247
+ ## Subpath exports
248
+
249
+ | Import | Purpose |
250
+ |---|---|
251
+ | `@qelos/integrator-next` | Everything (re-exports of all subpaths) |
252
+ | `@qelos/integrator-next/middleware` | Edge middleware (`qelosMiddleware`, `createQelosMiddleware`) |
253
+ | `@qelos/integrator-next/context` | Server-component / route-handler context (`getQelosContext`, `getStoredQelosContext`) |
254
+ | `@qelos/integrator-next/route` | App Router route wrapper (`withQelosRoute`) |
255
+ | `@qelos/integrator-next/pages` | Pages Router wrappers (`withQelosApi`, `withQelosSSR`) |
256
+ | `@qelos/integrator-next/types` | Shared types |
@@ -0,0 +1,55 @@
1
+ import type { IUser } from '@qelos/sdk/dist/authentication';
2
+ import type { IWorkspace } from '@qelos/sdk/workspaces';
3
+ import type { QelosNextConfig, QelosRequestContext, TokenRefreshHook } from './types';
4
+ /**
5
+ * Run `fn` with `ctx` as the active Qelos request context. Code anywhere in
6
+ * the resulting async stack can call `getStoredQelosContext()` to retrieve it
7
+ * without prop-drilling.
8
+ */
9
+ export declare function runWithQelosContext<T>(ctx: QelosRequestContext, fn: () => T | Promise<T>): T | Promise<T>;
10
+ /**
11
+ * Return the Qelos request context stored on the current async stack, or
12
+ * `null` when no context has been entered.
13
+ */
14
+ export declare function getStoredQelosContext(): QelosRequestContext | null;
15
+ export interface GetQelosContextOptions {
16
+ config: QelosNextConfig;
17
+ /**
18
+ * Hook invoked after a successful token refresh. App Router server
19
+ * components and route handlers cannot mutate cookies after rendering
20
+ * starts, so the default is a no-op. Callers handling refresh through a
21
+ * route handler can override this to call `cookies().set(...)`.
22
+ */
23
+ onTokenRefresh?: TokenRefreshHook<null>;
24
+ /**
25
+ * Resolve the active workspace for a request. Defaults to picking the first
26
+ * workspace returned from `sdk.workspaces.getList()`.
27
+ */
28
+ resolveWorkspace?: (params: {
29
+ user: IUser;
30
+ workspaces: IWorkspace[];
31
+ }) => IWorkspace | null | Promise<IWorkspace | null>;
32
+ }
33
+ /**
34
+ * Identify the user / workspace for the current App Router request and
35
+ * return the Qelos context. Safe to call multiple times within the same
36
+ * request — each call performs its own SDK round-trip, so wrap it with
37
+ * React's `cache()` if you want a single resolution per render.
38
+ *
39
+ * Calling without arguments uses the env-derived config (see
40
+ * {@link loadQelosConfigFromEnv}); pass `{ config }` explicitly when you
41
+ * need full control or want to override resolution behavior.
42
+ */
43
+ export declare function getQelosContext(options?: GetQelosContextOptions): Promise<QelosRequestContext>;
44
+ /**
45
+ * Convenience wrapper: resolve the Qelos context, store it in
46
+ * AsyncLocalStorage, and run `fn` inside that scope. Useful as the top of an
47
+ * App Router route handler when you want downstream code (utility modules,
48
+ * etc.) to read the context via `getStoredQelosContext()`.
49
+ */
50
+ export declare function withQelosContext<T>(options: GetQelosContextOptions, fn: (ctx: QelosRequestContext) => T | Promise<T>): Promise<T>;
51
+ /**
52
+ * Throw a 401-like error if the current stored context has no user.
53
+ * Returns the context for chaining.
54
+ */
55
+ export declare function requireQelosUser(): QelosRequestContext;
@@ -0,0 +1,131 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { getDefaultQelosConfig } from './env-config';
3
+ import { createRequestSdk } from './sdk-factory';
4
+ const DEFAULT_ACCESS_COOKIE = 'q_access_token';
5
+ const DEFAULT_REFRESH_COOKIE = 'q_refresh_token';
6
+ const storage = new AsyncLocalStorage();
7
+ /**
8
+ * Run `fn` with `ctx` as the active Qelos request context. Code anywhere in
9
+ * the resulting async stack can call `getStoredQelosContext()` to retrieve it
10
+ * without prop-drilling.
11
+ */
12
+ export function runWithQelosContext(ctx, fn) {
13
+ return storage.run(ctx, fn);
14
+ }
15
+ /**
16
+ * Return the Qelos request context stored on the current async stack, or
17
+ * `null` when no context has been entered.
18
+ */
19
+ export function getStoredQelosContext() {
20
+ return storage.getStore() || null;
21
+ }
22
+ let nextHeadersModulePromise = null;
23
+ function loadNextHeaders() {
24
+ if (!nextHeadersModulePromise) {
25
+ nextHeadersModulePromise = (async () => {
26
+ const mod = (await import('next/headers'));
27
+ return mod;
28
+ })();
29
+ }
30
+ return nextHeadersModulePromise;
31
+ }
32
+ async function readTokensFromHeaders(config) {
33
+ const { cookies, headers } = await loadNextHeaders();
34
+ const accessCookie = config.accessTokenCookie || DEFAULT_ACCESS_COOKIE;
35
+ const refreshCookie = config.refreshTokenCookie || DEFAULT_REFRESH_COOKIE;
36
+ const jar = await cookies();
37
+ const hdr = await headers();
38
+ const cookieAccess = jar.get(accessCookie)?.value;
39
+ const cookieRefresh = jar.get(refreshCookie)?.value;
40
+ const authHeader = hdr.get('authorization');
41
+ const headerAccess = authHeader && authHeader.toLowerCase().startsWith('bearer ')
42
+ ? authHeader.slice(7).trim()
43
+ : undefined;
44
+ return {
45
+ accessToken: headerAccess || cookieAccess || undefined,
46
+ refreshToken: cookieRefresh || undefined,
47
+ };
48
+ }
49
+ /**
50
+ * Identify the user / workspace for the current App Router request and
51
+ * return the Qelos context. Safe to call multiple times within the same
52
+ * request — each call performs its own SDK round-trip, so wrap it with
53
+ * React's `cache()` if you want a single resolution per render.
54
+ *
55
+ * Calling without arguments uses the env-derived config (see
56
+ * {@link loadQelosConfigFromEnv}); pass `{ config }` explicitly when you
57
+ * need full control or want to override resolution behavior.
58
+ */
59
+ export async function getQelosContext(options) {
60
+ const resolved = options ?? { config: getDefaultQelosConfig() };
61
+ const { config, resolveWorkspace } = resolved;
62
+ const tokens = await readTokensFromHeaders(config);
63
+ const sdk = createRequestSdk({
64
+ config,
65
+ tokens,
66
+ refreshTarget: null,
67
+ onTokenRefresh: resolved.onTokenRefresh,
68
+ });
69
+ const ctx = {
70
+ user: null,
71
+ workspace: null,
72
+ workspaces: [],
73
+ sdk,
74
+ tokens,
75
+ };
76
+ const hasAuthMaterial = Boolean(config.apiToken || tokens.accessToken || tokens.refreshToken);
77
+ if (!hasAuthMaterial) {
78
+ return ctx;
79
+ }
80
+ try {
81
+ ctx.user = await sdk.authentication.getLoggedInUser();
82
+ }
83
+ catch {
84
+ return ctx;
85
+ }
86
+ if (!ctx.user)
87
+ return ctx;
88
+ try {
89
+ ctx.workspaces = await sdk.workspaces.getList();
90
+ }
91
+ catch {
92
+ ctx.workspaces = [];
93
+ }
94
+ if (ctx.workspaces.length) {
95
+ if (resolveWorkspace) {
96
+ ctx.workspace =
97
+ (await resolveWorkspace({
98
+ user: ctx.user,
99
+ workspaces: ctx.workspaces,
100
+ })) || null;
101
+ }
102
+ else {
103
+ ctx.workspace = ctx.workspaces[0] || null;
104
+ }
105
+ }
106
+ return ctx;
107
+ }
108
+ /**
109
+ * Convenience wrapper: resolve the Qelos context, store it in
110
+ * AsyncLocalStorage, and run `fn` inside that scope. Useful as the top of an
111
+ * App Router route handler when you want downstream code (utility modules,
112
+ * etc.) to read the context via `getStoredQelosContext()`.
113
+ */
114
+ export async function withQelosContext(options, fn) {
115
+ const ctx = await getQelosContext(options);
116
+ return await storage.run(ctx, () => Promise.resolve(fn(ctx)));
117
+ }
118
+ /**
119
+ * Throw a 401-like error if the current stored context has no user.
120
+ * Returns the context for chaining.
121
+ */
122
+ export function requireQelosUser() {
123
+ const ctx = getStoredQelosContext();
124
+ if (!ctx || !ctx.user) {
125
+ const err = new Error('UNAUTHORIZED');
126
+ err.status = 401;
127
+ throw err;
128
+ }
129
+ return ctx;
130
+ }
131
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { QelosNextConfig } from './types';
2
+ /**
3
+ * Build a {@link QelosNextConfig} from `process.env`. Used by the
4
+ * convention-over-configuration entry points (`qelosMiddleware`, the
5
+ * no-arg `getQelosContext()`) so consumers can re-export the integrator
6
+ * without writing a factory call.
7
+ *
8
+ * Recognized variables:
9
+ *
10
+ * - `QELOS_APP_URL` (required) — Qelos backend base URL.
11
+ * - `QELOS_API_TOKEN` — service-to-service token (skips refresh logic).
12
+ * - `QELOS_ACCESS_TOKEN_COOKIE` — defaults to `q_access_token`.
13
+ * - `QELOS_REFRESH_TOKEN_COOKIE` — defaults to `q_refresh_token`.
14
+ * - `QELOS_REQUIRE_AUTH` — `"true"` to reject anonymous requests.
15
+ * - `QELOS_SKIP_PATHS` — comma-separated path prefixes to bypass.
16
+ */
17
+ export declare function loadQelosConfigFromEnv(env?: NodeJS.ProcessEnv): QelosNextConfig;
18
+ /**
19
+ * Return the env-derived config, memoized after the first call. The cache is
20
+ * scoped to the current Node process — Next.js dev-mode reloads create a new
21
+ * module instance, so changes to env vars are picked up on restart.
22
+ */
23
+ export declare function getDefaultQelosConfig(): QelosNextConfig;
24
+ /**
25
+ * Reset the memoized env-derived config. Exported for tests; not part of the
26
+ * stable surface.
27
+ */
28
+ export declare function __resetDefaultQelosConfig(): void;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Build a {@link QelosNextConfig} from `process.env`. Used by the
3
+ * convention-over-configuration entry points (`qelosMiddleware`, the
4
+ * no-arg `getQelosContext()`) so consumers can re-export the integrator
5
+ * without writing a factory call.
6
+ *
7
+ * Recognized variables:
8
+ *
9
+ * - `QELOS_APP_URL` (required) — Qelos backend base URL.
10
+ * - `QELOS_API_TOKEN` — service-to-service token (skips refresh logic).
11
+ * - `QELOS_ACCESS_TOKEN_COOKIE` — defaults to `q_access_token`.
12
+ * - `QELOS_REFRESH_TOKEN_COOKIE` — defaults to `q_refresh_token`.
13
+ * - `QELOS_REQUIRE_AUTH` — `"true"` to reject anonymous requests.
14
+ * - `QELOS_SKIP_PATHS` — comma-separated path prefixes to bypass.
15
+ */
16
+ export function loadQelosConfigFromEnv(env = process.env) {
17
+ const appUrl = env.QELOS_APP_URL;
18
+ if (!appUrl) {
19
+ throw new Error('@qelos/integrator-next: QELOS_APP_URL is required when using the default ' +
20
+ 'qelosMiddleware / getQelosContext() exports. Set it in your environment ' +
21
+ 'or call createQelosMiddleware({ config: ... }) explicitly.');
22
+ }
23
+ const config = { appUrl };
24
+ if (env.QELOS_API_TOKEN)
25
+ config.apiToken = env.QELOS_API_TOKEN;
26
+ if (env.QELOS_ACCESS_TOKEN_COOKIE)
27
+ config.accessTokenCookie = env.QELOS_ACCESS_TOKEN_COOKIE;
28
+ if (env.QELOS_REFRESH_TOKEN_COOKIE)
29
+ config.refreshTokenCookie = env.QELOS_REFRESH_TOKEN_COOKIE;
30
+ if (env.QELOS_REQUIRE_AUTH === 'true' || env.QELOS_REQUIRE_AUTH === '1') {
31
+ config.requireAuth = true;
32
+ }
33
+ if (env.QELOS_SKIP_PATHS) {
34
+ config.skipPaths = env.QELOS_SKIP_PATHS.split(',')
35
+ .map((p) => p.trim())
36
+ .filter(Boolean);
37
+ }
38
+ return config;
39
+ }
40
+ let cachedConfig = null;
41
+ /**
42
+ * Return the env-derived config, memoized after the first call. The cache is
43
+ * scoped to the current Node process — Next.js dev-mode reloads create a new
44
+ * module instance, so changes to env vars are picked up on restart.
45
+ */
46
+ export function getDefaultQelosConfig() {
47
+ if (!cachedConfig) {
48
+ cachedConfig = loadQelosConfigFromEnv();
49
+ }
50
+ return cachedConfig;
51
+ }
52
+ /**
53
+ * Reset the memoized env-derived config. Exported for tests; not part of the
54
+ * stable surface.
55
+ */
56
+ export function __resetDefaultQelosConfig() {
57
+ cachedConfig = null;
58
+ }
59
+ //# sourceMappingURL=env-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-config.js","sourceRoot":"","sources":["../src/env-config.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACzE,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC;IACjC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,2EAA2E;YACzE,0EAA0E;YAC1E,4DAA4D,CAC/D,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAoB,EAAE,MAAM,EAAE,CAAC;IAC3C,IAAI,GAAG,CAAC,eAAe;QAAE,MAAM,CAAC,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC;IAC/D,IAAI,GAAG,CAAC,yBAAyB;QAAE,MAAM,CAAC,iBAAiB,GAAG,GAAG,CAAC,yBAAyB,CAAC;IAC5F,IAAI,GAAG,CAAC,0BAA0B;QAAE,MAAM,CAAC,kBAAkB,GAAG,GAAG,CAAC,0BAA0B,CAAC;IAC/F,IAAI,GAAG,CAAC,kBAAkB,KAAK,MAAM,IAAI,GAAG,CAAC,kBAAkB,KAAK,GAAG,EAAE,CAAC;QACxE,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC;IAC5B,CAAC;IACD,IAAI,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACzB,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC;aAC/C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,IAAI,YAAY,GAA2B,IAAI,CAAC;AAEhD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB;IACnC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,sBAAsB,EAAE,CAAC;IAC1C,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB;IACvC,YAAY,GAAG,IAAI,CAAC;AACtB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { IUser } from '@qelos/sdk/dist/authentication';
2
+ import type { IWorkspace } from '@qelos/sdk/workspaces';
3
+ /**
4
+ * Replace forwarded identity headers on the outbound request: strip any
5
+ * client-supplied values first, then attach ids resolved by the integrator.
6
+ */
7
+ export declare function applyQelosForwardHeaders(forwardedHeaders: Headers, user: IUser | null, workspace: IWorkspace | null): void;
@@ -0,0 +1,16 @@
1
+ import { QELOS_USER_HEADER, QELOS_WORKSPACE_HEADER } from './types';
2
+ /**
3
+ * Replace forwarded identity headers on the outbound request: strip any
4
+ * client-supplied values first, then attach ids resolved by the integrator.
5
+ */
6
+ export function applyQelosForwardHeaders(forwardedHeaders, user, workspace) {
7
+ forwardedHeaders.delete(QELOS_USER_HEADER);
8
+ forwardedHeaders.delete(QELOS_WORKSPACE_HEADER);
9
+ if (user) {
10
+ forwardedHeaders.set(QELOS_USER_HEADER, String(user._id ?? ''));
11
+ }
12
+ if (workspace) {
13
+ forwardedHeaders.set(QELOS_WORKSPACE_HEADER, String(workspace._id ?? ''));
14
+ }
15
+ }
16
+ //# sourceMappingURL=forward-headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward-headers.js","sourceRoot":"","sources":["../src/forward-headers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,gBAAyB,EACzB,IAAkB,EAClB,SAA4B;IAE5B,gBAAgB,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC3C,gBAAgB,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAChD,IAAI,IAAI,EAAE,CAAC;QACT,gBAAgB,CAAC,GAAG,CAClB,iBAAiB,EACjB,MAAM,CAAE,IAA0B,CAAC,GAAG,IAAI,EAAE,CAAC,CAC9C,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,EAAE,CAAC;QACd,gBAAgB,CAAC,GAAG,CAClB,sBAAsB,EACtB,MAAM,CAAE,SAA+B,CAAC,GAAG,IAAI,EAAE,CAAC,CACnD,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { createQelosMiddleware, qelosMiddleware, type CreateMiddlewareOptions, type MiddlewareRefreshTarget, type PendingCookie, type QelosMiddleware, } from './middleware';
2
+ export { loadQelosConfigFromEnv, getDefaultQelosConfig, } from './env-config';
3
+ export { createRequestSdk, type CreateSdkParams, } from './sdk-factory';
4
+ export { getQelosContext, withQelosContext, runWithQelosContext, getStoredQelosContext, requireQelosUser, type GetQelosContextOptions, } from './context';
5
+ export { withQelosApi, withQelosSSR, requireQelosApiUser, type PagesIntegratorOptions, } from './pages';
6
+ export { withQelosRoute, type AppRouteHandler, type QelosAppRouteHandler, } from './route';
7
+ export { QELOS_USER_HEADER, QELOS_WORKSPACE_HEADER, type QelosNextConfig, type QelosRequestContext, type QelosTokenPair, type ResolvedTokens, type TokenRefreshContext, type TokenRefreshHook, } from './types';
8
+ export { completeSocialAuthCallback, applySocialAuthCookiesToServerResponse, getSocialAuthSetCookieParts, parseSocialCallbackRefreshToken, type SocialAuthCallbackPayload, type SocialCallbackInput, } from './social-auth';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { createQelosMiddleware, qelosMiddleware, } from './middleware';
2
+ export { loadQelosConfigFromEnv, getDefaultQelosConfig, } from './env-config';
3
+ export { createRequestSdk, } from './sdk-factory';
4
+ export { getQelosContext, withQelosContext, runWithQelosContext, getStoredQelosContext, requireQelosUser, } from './context';
5
+ export { withQelosApi, withQelosSSR, requireQelosApiUser, } from './pages';
6
+ export { withQelosRoute, } from './route';
7
+ export { QELOS_USER_HEADER, QELOS_WORKSPACE_HEADER, } from './types';
8
+ export { completeSocialAuthCallback, applySocialAuthCookiesToServerResponse, getSocialAuthSetCookieParts, parseSocialCallbackRefreshToken, } from './social-auth';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,eAAe,GAKhB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,gBAAgB,GAEjB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GAEjB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,mBAAmB,GAEpB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,cAAc,GAGf,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,iBAAiB,EACjB,sBAAsB,GAOvB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,0BAA0B,EAC1B,sCAAsC,EACtC,2BAA2B,EAC3B,+BAA+B,GAGhC,MAAM,eAAe,CAAC"}