@useupup/next 3.3.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Devino
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @useupup/next
2
+
3
+ Next.js integration for the [upup](https://github.com/DevinoSolutions/upup) uploader. One install gives you the client UI and the server handlers, split across two entry points so the AWS SDK never reaches your client bundle.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm i @useupup/next
9
+ ```
10
+
11
+ ## Client (App Router or Pages Router)
12
+
13
+ ```tsx
14
+ import { UpupUploader } from '@useupup/next'
15
+ import '@useupup/next/styles'
16
+
17
+ export default function Page() {
18
+ return <UpupUploader mode="server" serverUrl="/api/upup" />
19
+ }
20
+ ```
21
+
22
+ `@useupup/next` re-exports the full `@useupup/react` client, so client mode
23
+ (`<UpupUploader provider="aws" uploadEndpoint="/api/upload-token" />`, no
24
+ `@useupup/server`) works too.
25
+
26
+ ## Server — App Router (`app/api/upup/[...path]/route.ts`)
27
+
28
+ ```ts
29
+ import { createUpupNextHandler, defineUpupConfig } from '@useupup/next/server'
30
+
31
+ export const dynamic = 'force-dynamic'
32
+ export const maxDuration = 60
33
+
34
+ export const { GET, POST, PUT, DELETE } = createUpupNextHandler(
35
+ defineUpupConfig({
36
+ storage: {
37
+ type: 'aws',
38
+ bucket: process.env.S3_BUCKET!,
39
+ region: process.env.S3_REGION! /* creds... */,
40
+ },
41
+ // REQUIRED — HMAC-signs upload tokens. Must be ≥16 chars and identical
42
+ // on every instance; the handler THROWS at construction time without it.
43
+ uploadTokenSecret: process.env.UPUP_UPLOAD_TOKEN_SECRET!,
44
+ }),
45
+ )
46
+ ```
47
+
48
+ ## Server — Pages Router (`pages/api/upup/[...path].ts`)
49
+
50
+ ```ts
51
+ import { createUpupPagesHandler, defineUpupConfig } from '@useupup/next/server'
52
+
53
+ export const config = { api: { bodyParser: false } } // REQUIRED — we read the raw body
54
+
55
+ export default createUpupPagesHandler(
56
+ defineUpupConfig({
57
+ storage: {
58
+ type: 'aws',
59
+ bucket: process.env.S3_BUCKET!,
60
+ region: process.env.S3_REGION!,
61
+ },
62
+ // REQUIRED (≥16 chars) — the handler throws at construction without it.
63
+ uploadTokenSecret: process.env.UPUP_UPLOAD_TOKEN_SECRET!,
64
+ }),
65
+ )
66
+ ```
67
+
68
+ ## Deploying to serverless (Vercel / Lambda / Netlify)
69
+
70
+ - **Persist OAuth state + tokens.** The default `InMemoryTokenStore` keeps OAuth
71
+ `state` and drive tokens in a process-local `Map`. On serverless the redirect and
72
+ callback can hit different instances, so login fails with `400 "Invalid or expired
73
+ state"`. Implement the `TokenStore { get, set, delete }` interface against Redis /
74
+ Upstash / KV and pass it as `config.tokenStore`.
75
+ - **Function timeout.** Server-mode drive→S3 transfer streams _through_ the function.
76
+ Set `maxDuration` (App Router segment config) and raise it toward your platform max
77
+ for large files. (Direct local uploads bypass the function via presigned PUT.)
78
+ - **Memory.** Server-mode drive→S3 transfers stream in fixed 5 MiB chunks (files
79
+ ≤ 5 MiB upload as a single PUT), so the per-transfer memory envelope is one chunk
80
+ regardless of file size — even 128–256 MB functions are fine. This bound is not
81
+ configurable by design.
82
+ - **Proxy/CDN origin.** Behind a proxy, pass `createUpupNextHandler(config, { baseUrl })`
83
+ (or `{ trustProxy: true }` to read `x-forwarded-*`) so the OAuth callback URL is
84
+ correct. No-op on Vercel App Router (`req.url` is already public).
85
+ - **OAuth redirect URIs.** Register your deployed callback URL
86
+ (`https://<your-domain>/api/upup/auth/<provider>/cb`) in each drive provider's OAuth
87
+ app (Google / Dropbox / OneDrive / Box). It must match the origin that
88
+ `baseUrl`/`trustProxy` resolves to, or login fails with `redirect_uri_mismatch`.
89
+ - **Bundling.** Add `serverExternalPackages: ['@aws-sdk/client-s3', '@aws-sdk/s3-request-presigner']`
90
+ to `next.config`.
91
+
92
+ ## Self-hosted (`next start` / Docker)
93
+
94
+ - The in-memory store works for a single instance but breaks across replicas/restarts
95
+ — use a shared `TokenStore` when scaling horizontally.
96
+ - No function timeout, so large transfers are fine; memory stays bounded at ~5 MiB
97
+ per concurrent transfer.
98
+
99
+ ## S3 bucket CORS
100
+
101
+ Presigned uploads go browser→S3 directly, so the bucket's CORS policy must allow your
102
+ site origin for `PUT` (and `GET` for previews). This is bucket configuration, not
103
+ `@useupup/next` code.
104
+
105
+ ## Links
106
+
107
+ - [Next.js quickstart](https://useupup.com/docs/quickstarts/next)
108
+ - [Documentation](https://useupup.com/docs/)
109
+ - [Source & monorepo](https://github.com/DevinoSolutions/upup)
110
+
111
+ ## License
112
+
113
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.ts
20
+ var index_exports = {};
21
+ module.exports = __toCommonJS(index_exports);
22
+ __reExport(index_exports, require("@useupup/react"), module.exports);
23
+ // Annotate the CommonJS export names for ESM import in node:
24
+ 0 && (module.exports = {
25
+ ...require("@useupup/react")
26
+ });
27
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["'use client'\r\nexport * from '@useupup/react'\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AACA,0BAAc,2BADd;","names":[]}
@@ -0,0 +1 @@
1
+ export * from '@useupup/react';
@@ -0,0 +1 @@
1
+ export * from '@useupup/react';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/index.ts
5
+ export * from "@useupup/react";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["'use client'\r\nexport * from '@useupup/react'\r\n"],"mappings":";;;;AACA,cAAc;","names":[]}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ InMemoryTokenStore: () => import_server2.InMemoryTokenStore,
24
+ createUpupNextHandler: () => import_next.createUpupNextHandler,
25
+ createUpupPagesHandler: () => createUpupPagesHandler,
26
+ defineUpupConfig: () => defineUpupConfig,
27
+ normalizeRequestOrigin: () => import_next.normalizeRequestOrigin,
28
+ resolveOrigin: () => import_next.resolveOrigin
29
+ });
30
+ module.exports = __toCommonJS(server_exports);
31
+ var import_next = require("@useupup/server/next");
32
+
33
+ // src/pages-handler.ts
34
+ var import_server = require("@useupup/server");
35
+ var import_node_bridge = require("@useupup/server/node-bridge");
36
+ function firstHeaderValue(value) {
37
+ const raw = Array.isArray(value) ? value[0] : value;
38
+ if (!raw) return void 0;
39
+ const first = raw.split(",")[0]?.trim();
40
+ return first || void 0;
41
+ }
42
+ function resolveBase(req, opts) {
43
+ if (opts?.baseUrl) return new URL(opts.baseUrl).origin;
44
+ const xfHost = firstHeaderValue(req.headers["x-forwarded-host"]);
45
+ const host = opts?.trustProxy && xfHost || req.headers.host || "localhost";
46
+ const xfProto = firstHeaderValue(req.headers["x-forwarded-proto"]);
47
+ const isLocal = host.startsWith("localhost") || host.startsWith("127.");
48
+ const proto = opts?.trustProxy && xfProto || (isLocal ? "http" : "https");
49
+ return `${proto}://${host}`;
50
+ }
51
+ async function readBody(req) {
52
+ const method = (req.method ?? "GET").toUpperCase();
53
+ if (method === "GET" || method === "HEAD") return void 0;
54
+ const chunks = [];
55
+ for await (const chunk of req) {
56
+ chunks.push(
57
+ typeof chunk === "string" ? Buffer.from(chunk) : chunk
58
+ );
59
+ }
60
+ return chunks.length ? new Uint8Array(Buffer.concat(chunks)) : void 0;
61
+ }
62
+ function createUpupPagesHandler(config, opts) {
63
+ const handler = (0, import_server.createUpupHandler)(config);
64
+ return async (req, res) => {
65
+ try {
66
+ const base = resolveBase(req, opts);
67
+ const body = await readBody(req);
68
+ const webReq = (0, import_node_bridge.toWebRequest)({
69
+ url: new URL(req.url ?? "/", base).toString(),
70
+ method: req.method ?? "GET",
71
+ headers: req.headers,
72
+ body
73
+ });
74
+ const webRes = await handler(webReq);
75
+ await (0, import_node_bridge.writeWebResponse)(
76
+ {
77
+ status: (c) => {
78
+ res.status(c);
79
+ },
80
+ setHeader: (k, v) => {
81
+ res.setHeader(k, v);
82
+ },
83
+ send: (b) => {
84
+ res.send(b);
85
+ }
86
+ },
87
+ webRes
88
+ );
89
+ } catch (err) {
90
+ const message = err instanceof Error ? err.message : "Internal error";
91
+ res.status(500).json({ error: message });
92
+ }
93
+ };
94
+ }
95
+
96
+ // src/define-config.ts
97
+ function defineUpupConfig(config) {
98
+ return config;
99
+ }
100
+
101
+ // src/server.ts
102
+ var import_server2 = require("@useupup/server");
103
+ // Annotate the CommonJS export names for ESM import in node:
104
+ 0 && (module.exports = {
105
+ InMemoryTokenStore,
106
+ createUpupNextHandler,
107
+ createUpupPagesHandler,
108
+ defineUpupConfig,
109
+ normalizeRequestOrigin,
110
+ resolveOrigin
111
+ });
112
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/pages-handler.ts","../src/define-config.ts"],"sourcesContent":["// App Router handler + origin utilities live in @useupup/server/next; this entry\r\n// re-exports them and adds the Pages Router adapter + config helper (Node-only).\r\nexport {\r\n createUpupNextHandler,\r\n normalizeRequestOrigin,\r\n resolveOrigin,\r\n} from '@useupup/server/next'\r\nexport type { UpupNextOptions } from '@useupup/server/next'\r\n\r\nexport { createUpupPagesHandler } from './pages-handler'\r\nexport { defineUpupConfig } from './define-config'\r\n\r\n// Token-store utilities surfaced for the persistent-store guidance.\r\nexport { InMemoryTokenStore } from '@useupup/server'\r\nexport type { UpupServerConfig, TokenStore, DriveTokens } from '@useupup/server'\r\n","import type { NextApiRequest, NextApiResponse } from 'next'\r\nimport { createUpupHandler } from '@useupup/server'\r\nimport type { UpupServerConfig } from '@useupup/server'\r\nimport type { UpupNextOptions } from '@useupup/server/next'\r\nimport { toWebRequest, writeWebResponse } from '@useupup/server/node-bridge'\r\n\r\nfunction firstHeaderValue(\r\n value: string | string[] | undefined,\r\n): string | undefined {\r\n const raw = Array.isArray(value) ? value[0] : value\r\n if (!raw) return undefined\r\n const first = raw.split(',')[0]?.trim()\r\n return first || undefined\r\n}\r\n\r\n/** Resolve the public origin for the Web Request we hand to the core handler. */\r\nfunction resolveBase(req: NextApiRequest, opts?: UpupNextOptions): string {\r\n if (opts?.baseUrl) return new URL(opts.baseUrl).origin\r\n const xfHost = firstHeaderValue(req.headers['x-forwarded-host'])\r\n const host = (opts?.trustProxy && xfHost) || req.headers.host || 'localhost'\r\n const xfProto = firstHeaderValue(req.headers['x-forwarded-proto'])\r\n const isLocal = host.startsWith('localhost') || host.startsWith('127.')\r\n const proto = (opts?.trustProxy && xfProto) || (isLocal ? 'http' : 'https')\r\n return `${proto}://${host}`\r\n}\r\n\r\n/**\r\n * Yields the raw body as a plain `Uint8Array`, never the `Buffer` we assemble.\r\n * Under @types/node >=22 a `Buffer` types as `Buffer<ArrayBufferLike>`, which is\r\n * not assignable to `BodyInit` — copying into a fresh `Uint8Array` yields\r\n * `Uint8Array<ArrayBuffer>`, which every @types/node version accepts. The return\r\n * type is `RequestInit['body']` (the bridge's own parameter type) rather than a\r\n * bare `Uint8Array`, because bare `Uint8Array` means `Uint8Array<ArrayBufferLike>`\r\n * and would reintroduce the same mismatch at the annotation.\r\n */\r\nasync function readBody(\r\n req: NextApiRequest,\r\n): Promise<RequestInit['body'] | undefined> {\r\n const method = (req.method ?? 'GET').toUpperCase()\r\n if (method === 'GET' || method === 'HEAD') return undefined\r\n const chunks: Buffer[] = []\r\n for await (const chunk of req) {\r\n chunks.push(\r\n typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer),\r\n )\r\n }\r\n return chunks.length ? new Uint8Array(Buffer.concat(chunks)) : undefined\r\n}\r\n\r\n/**\r\n * Pages Router (`pages/api/...`) adapter. Bridges Node req/res to the\r\n * framework-agnostic Web handler. The route MUST set\r\n * `export const config = { api: { bodyParser: false } }` so we receive the\r\n * raw request body. `opts.baseUrl` / `trustProxy` correct the OAuth callback\r\n * origin behind a proxy/CDN.\r\n */\r\nexport function createUpupPagesHandler(\r\n config: UpupServerConfig,\r\n opts?: UpupNextOptions,\r\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\r\n const handler = createUpupHandler(config)\r\n return async (req, res) => {\r\n try {\r\n const base = resolveBase(req, opts)\r\n const body = await readBody(req)\r\n const webReq = toWebRequest({\r\n url: new URL(req.url ?? '/', base).toString(),\r\n method: req.method ?? 'GET',\r\n headers: req.headers,\r\n body,\r\n })\r\n const webRes = await handler(webReq)\r\n await writeWebResponse(\r\n {\r\n status: c => {\r\n res.status(c)\r\n },\r\n setHeader: (k, v) => {\r\n res.setHeader(k, v)\r\n },\r\n send: b => {\r\n res.send(b)\r\n },\r\n },\r\n webRes,\r\n )\r\n } catch (err: unknown) {\r\n // upup-catch: Pages Router top-level catch — maps handler errors to 500 JSON\r\n const message =\r\n err instanceof Error ? err.message : 'Internal error'\r\n res.status(500).json({ error: message })\r\n }\r\n }\r\n}\r\n","import type { UpupServerConfig } from '@useupup/server'\r\n\r\n/**\r\n * Typed helper for authoring a server config with full editor autocomplete and\r\n * type-checking. Returns the config unchanged — a thin pass-through.\r\n *\r\n * Required-field validation is intentionally NOT performed here (F-852): it\r\n * lives at construct time inside @useupup/server's `createUpupHandler`, so it runs\r\n * for EVERY caller — including apps that call `createUpupNextHandler({...})`\r\n * directly without this wrapper (playground/landing did exactly that). A\r\n * `defineUpupConfig` that validated on its own left those direct callers\r\n * unprotected, which was the original bug this fold-in closes.\r\n */\r\nexport function defineUpupConfig(config: UpupServerConfig): UpupServerConfig {\r\n return config\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,kBAIO;;;ACLP,oBAAkC;AAGlC,yBAA+C;AAE/C,SAAS,iBACL,OACkB;AAClB,QAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAC9C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AACtC,SAAO,SAAS;AACpB;AAGA,SAAS,YAAY,KAAqB,MAAgC;AACtE,MAAI,MAAM,QAAS,QAAO,IAAI,IAAI,KAAK,OAAO,EAAE;AAChD,QAAM,SAAS,iBAAiB,IAAI,QAAQ,kBAAkB,CAAC;AAC/D,QAAM,OAAQ,MAAM,cAAc,UAAW,IAAI,QAAQ,QAAQ;AACjE,QAAM,UAAU,iBAAiB,IAAI,QAAQ,mBAAmB,CAAC;AACjE,QAAM,UAAU,KAAK,WAAW,WAAW,KAAK,KAAK,WAAW,MAAM;AACtE,QAAM,QAAS,MAAM,cAAc,YAAa,UAAU,SAAS;AACnE,SAAO,GAAG,KAAK,MAAM,IAAI;AAC7B;AAWA,eAAe,SACX,KACwC;AACxC,QAAM,UAAU,IAAI,UAAU,OAAO,YAAY;AACjD,MAAI,WAAW,SAAS,WAAW,OAAQ,QAAO;AAClD,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,KAAK;AAC3B,WAAO;AAAA,MACH,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAK;AAAA,IACtD;AAAA,EACJ;AACA,SAAO,OAAO,SAAS,IAAI,WAAW,OAAO,OAAO,MAAM,CAAC,IAAI;AACnE;AASO,SAAS,uBACZ,QACA,MAC4D;AAC5D,QAAM,cAAU,iCAAkB,MAAM;AACxC,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI;AACA,YAAM,OAAO,YAAY,KAAK,IAAI;AAClC,YAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAM,aAAS,iCAAa;AAAA,QACxB,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,IAAI,EAAE,SAAS;AAAA,QAC5C,QAAQ,IAAI,UAAU;AAAA,QACtB,SAAS,IAAI;AAAA,QACb;AAAA,MACJ,CAAC;AACD,YAAM,SAAS,MAAM,QAAQ,MAAM;AACnC,gBAAM;AAAA,QACF;AAAA,UACI,QAAQ,OAAK;AACT,gBAAI,OAAO,CAAC;AAAA,UAChB;AAAA,UACA,WAAW,CAAC,GAAG,MAAM;AACjB,gBAAI,UAAU,GAAG,CAAC;AAAA,UACtB;AAAA,UACA,MAAM,OAAK;AACP,gBAAI,KAAK,CAAC;AAAA,UACd;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAAA,IACJ,SAAS,KAAc;AAEnB,YAAM,UACF,eAAe,QAAQ,IAAI,UAAU;AACzC,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,QAAQ,CAAC;AAAA,IAC3C;AAAA,EACJ;AACJ;;;AChFO,SAAS,iBAAiB,QAA4C;AACzE,SAAO;AACX;;;AFFA,IAAAA,iBAAmC;","names":["import_server"]}
@@ -0,0 +1,29 @@
1
+ import { UpupNextOptions } from '@useupup/server/next';
2
+ export { UpupNextOptions, createUpupNextHandler, normalizeRequestOrigin, resolveOrigin } from '@useupup/server/next';
3
+ import { NextApiRequest, NextApiResponse } from 'next';
4
+ import { UpupServerConfig } from '@useupup/server';
5
+ export { DriveTokens, InMemoryTokenStore, TokenStore, UpupServerConfig } from '@useupup/server';
6
+
7
+ /**
8
+ * Pages Router (`pages/api/...`) adapter. Bridges Node req/res to the
9
+ * framework-agnostic Web handler. The route MUST set
10
+ * `export const config = { api: { bodyParser: false } }` so we receive the
11
+ * raw request body. `opts.baseUrl` / `trustProxy` correct the OAuth callback
12
+ * origin behind a proxy/CDN.
13
+ */
14
+ declare function createUpupPagesHandler(config: UpupServerConfig, opts?: UpupNextOptions): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
15
+
16
+ /**
17
+ * Typed helper for authoring a server config with full editor autocomplete and
18
+ * type-checking. Returns the config unchanged — a thin pass-through.
19
+ *
20
+ * Required-field validation is intentionally NOT performed here (F-852): it
21
+ * lives at construct time inside @useupup/server's `createUpupHandler`, so it runs
22
+ * for EVERY caller — including apps that call `createUpupNextHandler({...})`
23
+ * directly without this wrapper (playground/landing did exactly that). A
24
+ * `defineUpupConfig` that validated on its own left those direct callers
25
+ * unprotected, which was the original bug this fold-in closes.
26
+ */
27
+ declare function defineUpupConfig(config: UpupServerConfig): UpupServerConfig;
28
+
29
+ export { createUpupPagesHandler, defineUpupConfig };
@@ -0,0 +1,29 @@
1
+ import { UpupNextOptions } from '@useupup/server/next';
2
+ export { UpupNextOptions, createUpupNextHandler, normalizeRequestOrigin, resolveOrigin } from '@useupup/server/next';
3
+ import { NextApiRequest, NextApiResponse } from 'next';
4
+ import { UpupServerConfig } from '@useupup/server';
5
+ export { DriveTokens, InMemoryTokenStore, TokenStore, UpupServerConfig } from '@useupup/server';
6
+
7
+ /**
8
+ * Pages Router (`pages/api/...`) adapter. Bridges Node req/res to the
9
+ * framework-agnostic Web handler. The route MUST set
10
+ * `export const config = { api: { bodyParser: false } }` so we receive the
11
+ * raw request body. `opts.baseUrl` / `trustProxy` correct the OAuth callback
12
+ * origin behind a proxy/CDN.
13
+ */
14
+ declare function createUpupPagesHandler(config: UpupServerConfig, opts?: UpupNextOptions): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
15
+
16
+ /**
17
+ * Typed helper for authoring a server config with full editor autocomplete and
18
+ * type-checking. Returns the config unchanged — a thin pass-through.
19
+ *
20
+ * Required-field validation is intentionally NOT performed here (F-852): it
21
+ * lives at construct time inside @useupup/server's `createUpupHandler`, so it runs
22
+ * for EVERY caller — including apps that call `createUpupNextHandler({...})`
23
+ * directly without this wrapper (playground/landing did exactly that). A
24
+ * `defineUpupConfig` that validated on its own left those direct callers
25
+ * unprotected, which was the original bug this fold-in closes.
26
+ */
27
+ declare function defineUpupConfig(config: UpupServerConfig): UpupServerConfig;
28
+
29
+ export { createUpupPagesHandler, defineUpupConfig };
package/dist/server.js ADDED
@@ -0,0 +1,86 @@
1
+ // src/server.ts
2
+ import {
3
+ createUpupNextHandler,
4
+ normalizeRequestOrigin,
5
+ resolveOrigin
6
+ } from "@useupup/server/next";
7
+
8
+ // src/pages-handler.ts
9
+ import { createUpupHandler } from "@useupup/server";
10
+ import { toWebRequest, writeWebResponse } from "@useupup/server/node-bridge";
11
+ function firstHeaderValue(value) {
12
+ const raw = Array.isArray(value) ? value[0] : value;
13
+ if (!raw) return void 0;
14
+ const first = raw.split(",")[0]?.trim();
15
+ return first || void 0;
16
+ }
17
+ function resolveBase(req, opts) {
18
+ if (opts?.baseUrl) return new URL(opts.baseUrl).origin;
19
+ const xfHost = firstHeaderValue(req.headers["x-forwarded-host"]);
20
+ const host = opts?.trustProxy && xfHost || req.headers.host || "localhost";
21
+ const xfProto = firstHeaderValue(req.headers["x-forwarded-proto"]);
22
+ const isLocal = host.startsWith("localhost") || host.startsWith("127.");
23
+ const proto = opts?.trustProxy && xfProto || (isLocal ? "http" : "https");
24
+ return `${proto}://${host}`;
25
+ }
26
+ async function readBody(req) {
27
+ const method = (req.method ?? "GET").toUpperCase();
28
+ if (method === "GET" || method === "HEAD") return void 0;
29
+ const chunks = [];
30
+ for await (const chunk of req) {
31
+ chunks.push(
32
+ typeof chunk === "string" ? Buffer.from(chunk) : chunk
33
+ );
34
+ }
35
+ return chunks.length ? new Uint8Array(Buffer.concat(chunks)) : void 0;
36
+ }
37
+ function createUpupPagesHandler(config, opts) {
38
+ const handler = createUpupHandler(config);
39
+ return async (req, res) => {
40
+ try {
41
+ const base = resolveBase(req, opts);
42
+ const body = await readBody(req);
43
+ const webReq = toWebRequest({
44
+ url: new URL(req.url ?? "/", base).toString(),
45
+ method: req.method ?? "GET",
46
+ headers: req.headers,
47
+ body
48
+ });
49
+ const webRes = await handler(webReq);
50
+ await writeWebResponse(
51
+ {
52
+ status: (c) => {
53
+ res.status(c);
54
+ },
55
+ setHeader: (k, v) => {
56
+ res.setHeader(k, v);
57
+ },
58
+ send: (b) => {
59
+ res.send(b);
60
+ }
61
+ },
62
+ webRes
63
+ );
64
+ } catch (err) {
65
+ const message = err instanceof Error ? err.message : "Internal error";
66
+ res.status(500).json({ error: message });
67
+ }
68
+ };
69
+ }
70
+
71
+ // src/define-config.ts
72
+ function defineUpupConfig(config) {
73
+ return config;
74
+ }
75
+
76
+ // src/server.ts
77
+ import { InMemoryTokenStore } from "@useupup/server";
78
+ export {
79
+ InMemoryTokenStore,
80
+ createUpupNextHandler,
81
+ createUpupPagesHandler,
82
+ defineUpupConfig,
83
+ normalizeRequestOrigin,
84
+ resolveOrigin
85
+ };
86
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/pages-handler.ts","../src/define-config.ts"],"sourcesContent":["// App Router handler + origin utilities live in @useupup/server/next; this entry\r\n// re-exports them and adds the Pages Router adapter + config helper (Node-only).\r\nexport {\r\n createUpupNextHandler,\r\n normalizeRequestOrigin,\r\n resolveOrigin,\r\n} from '@useupup/server/next'\r\nexport type { UpupNextOptions } from '@useupup/server/next'\r\n\r\nexport { createUpupPagesHandler } from './pages-handler'\r\nexport { defineUpupConfig } from './define-config'\r\n\r\n// Token-store utilities surfaced for the persistent-store guidance.\r\nexport { InMemoryTokenStore } from '@useupup/server'\r\nexport type { UpupServerConfig, TokenStore, DriveTokens } from '@useupup/server'\r\n","import type { NextApiRequest, NextApiResponse } from 'next'\r\nimport { createUpupHandler } from '@useupup/server'\r\nimport type { UpupServerConfig } from '@useupup/server'\r\nimport type { UpupNextOptions } from '@useupup/server/next'\r\nimport { toWebRequest, writeWebResponse } from '@useupup/server/node-bridge'\r\n\r\nfunction firstHeaderValue(\r\n value: string | string[] | undefined,\r\n): string | undefined {\r\n const raw = Array.isArray(value) ? value[0] : value\r\n if (!raw) return undefined\r\n const first = raw.split(',')[0]?.trim()\r\n return first || undefined\r\n}\r\n\r\n/** Resolve the public origin for the Web Request we hand to the core handler. */\r\nfunction resolveBase(req: NextApiRequest, opts?: UpupNextOptions): string {\r\n if (opts?.baseUrl) return new URL(opts.baseUrl).origin\r\n const xfHost = firstHeaderValue(req.headers['x-forwarded-host'])\r\n const host = (opts?.trustProxy && xfHost) || req.headers.host || 'localhost'\r\n const xfProto = firstHeaderValue(req.headers['x-forwarded-proto'])\r\n const isLocal = host.startsWith('localhost') || host.startsWith('127.')\r\n const proto = (opts?.trustProxy && xfProto) || (isLocal ? 'http' : 'https')\r\n return `${proto}://${host}`\r\n}\r\n\r\n/**\r\n * Yields the raw body as a plain `Uint8Array`, never the `Buffer` we assemble.\r\n * Under @types/node >=22 a `Buffer` types as `Buffer<ArrayBufferLike>`, which is\r\n * not assignable to `BodyInit` — copying into a fresh `Uint8Array` yields\r\n * `Uint8Array<ArrayBuffer>`, which every @types/node version accepts. The return\r\n * type is `RequestInit['body']` (the bridge's own parameter type) rather than a\r\n * bare `Uint8Array`, because bare `Uint8Array` means `Uint8Array<ArrayBufferLike>`\r\n * and would reintroduce the same mismatch at the annotation.\r\n */\r\nasync function readBody(\r\n req: NextApiRequest,\r\n): Promise<RequestInit['body'] | undefined> {\r\n const method = (req.method ?? 'GET').toUpperCase()\r\n if (method === 'GET' || method === 'HEAD') return undefined\r\n const chunks: Buffer[] = []\r\n for await (const chunk of req) {\r\n chunks.push(\r\n typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer),\r\n )\r\n }\r\n return chunks.length ? new Uint8Array(Buffer.concat(chunks)) : undefined\r\n}\r\n\r\n/**\r\n * Pages Router (`pages/api/...`) adapter. Bridges Node req/res to the\r\n * framework-agnostic Web handler. The route MUST set\r\n * `export const config = { api: { bodyParser: false } }` so we receive the\r\n * raw request body. `opts.baseUrl` / `trustProxy` correct the OAuth callback\r\n * origin behind a proxy/CDN.\r\n */\r\nexport function createUpupPagesHandler(\r\n config: UpupServerConfig,\r\n opts?: UpupNextOptions,\r\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\r\n const handler = createUpupHandler(config)\r\n return async (req, res) => {\r\n try {\r\n const base = resolveBase(req, opts)\r\n const body = await readBody(req)\r\n const webReq = toWebRequest({\r\n url: new URL(req.url ?? '/', base).toString(),\r\n method: req.method ?? 'GET',\r\n headers: req.headers,\r\n body,\r\n })\r\n const webRes = await handler(webReq)\r\n await writeWebResponse(\r\n {\r\n status: c => {\r\n res.status(c)\r\n },\r\n setHeader: (k, v) => {\r\n res.setHeader(k, v)\r\n },\r\n send: b => {\r\n res.send(b)\r\n },\r\n },\r\n webRes,\r\n )\r\n } catch (err: unknown) {\r\n // upup-catch: Pages Router top-level catch — maps handler errors to 500 JSON\r\n const message =\r\n err instanceof Error ? err.message : 'Internal error'\r\n res.status(500).json({ error: message })\r\n }\r\n }\r\n}\r\n","import type { UpupServerConfig } from '@useupup/server'\r\n\r\n/**\r\n * Typed helper for authoring a server config with full editor autocomplete and\r\n * type-checking. Returns the config unchanged — a thin pass-through.\r\n *\r\n * Required-field validation is intentionally NOT performed here (F-852): it\r\n * lives at construct time inside @useupup/server's `createUpupHandler`, so it runs\r\n * for EVERY caller — including apps that call `createUpupNextHandler({...})`\r\n * directly without this wrapper (playground/landing did exactly that). A\r\n * `defineUpupConfig` that validated on its own left those direct callers\r\n * unprotected, which was the original bug this fold-in closes.\r\n */\r\nexport function defineUpupConfig(config: UpupServerConfig): UpupServerConfig {\r\n return config\r\n}\r\n"],"mappings":";AAEA;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;;;ACLP,SAAS,yBAAyB;AAGlC,SAAS,cAAc,wBAAwB;AAE/C,SAAS,iBACL,OACkB;AAClB,QAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAC9C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AACtC,SAAO,SAAS;AACpB;AAGA,SAAS,YAAY,KAAqB,MAAgC;AACtE,MAAI,MAAM,QAAS,QAAO,IAAI,IAAI,KAAK,OAAO,EAAE;AAChD,QAAM,SAAS,iBAAiB,IAAI,QAAQ,kBAAkB,CAAC;AAC/D,QAAM,OAAQ,MAAM,cAAc,UAAW,IAAI,QAAQ,QAAQ;AACjE,QAAM,UAAU,iBAAiB,IAAI,QAAQ,mBAAmB,CAAC;AACjE,QAAM,UAAU,KAAK,WAAW,WAAW,KAAK,KAAK,WAAW,MAAM;AACtE,QAAM,QAAS,MAAM,cAAc,YAAa,UAAU,SAAS;AACnE,SAAO,GAAG,KAAK,MAAM,IAAI;AAC7B;AAWA,eAAe,SACX,KACwC;AACxC,QAAM,UAAU,IAAI,UAAU,OAAO,YAAY;AACjD,MAAI,WAAW,SAAS,WAAW,OAAQ,QAAO;AAClD,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,KAAK;AAC3B,WAAO;AAAA,MACH,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAK;AAAA,IACtD;AAAA,EACJ;AACA,SAAO,OAAO,SAAS,IAAI,WAAW,OAAO,OAAO,MAAM,CAAC,IAAI;AACnE;AASO,SAAS,uBACZ,QACA,MAC4D;AAC5D,QAAM,UAAU,kBAAkB,MAAM;AACxC,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI;AACA,YAAM,OAAO,YAAY,KAAK,IAAI;AAClC,YAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAM,SAAS,aAAa;AAAA,QACxB,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,IAAI,EAAE,SAAS;AAAA,QAC5C,QAAQ,IAAI,UAAU;AAAA,QACtB,SAAS,IAAI;AAAA,QACb;AAAA,MACJ,CAAC;AACD,YAAM,SAAS,MAAM,QAAQ,MAAM;AACnC,YAAM;AAAA,QACF;AAAA,UACI,QAAQ,OAAK;AACT,gBAAI,OAAO,CAAC;AAAA,UAChB;AAAA,UACA,WAAW,CAAC,GAAG,MAAM;AACjB,gBAAI,UAAU,GAAG,CAAC;AAAA,UACtB;AAAA,UACA,MAAM,OAAK;AACP,gBAAI,KAAK,CAAC;AAAA,UACd;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAAA,IACJ,SAAS,KAAc;AAEnB,YAAM,UACF,eAAe,QAAQ,IAAI,UAAU;AACzC,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,QAAQ,CAAC;AAAA,IAC3C;AAAA,EACJ;AACJ;;;AChFO,SAAS,iBAAiB,QAA4C;AACzE,SAAO;AACX;;;AFFA,SAAS,0BAA0B;","names":[]}
@@ -0,0 +1,4 @@
1
+ // Generated by scripts/emit-styles-dts.mjs — do not edit.
2
+ // Declares the './styles' subpath (runtime target: ./tailwind-prefixed.css) so
3
+ // TypeScript 6+ can resolve `import '@useupup/<framework>/styles'`.
4
+ export {}