@urun-sh/next 0.2.26 → 0.2.29

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/README.md CHANGED
@@ -3,27 +3,62 @@
3
3
  Next.js on-ramp for uRun **scoped client tokens** — never ship your org API
4
4
  key to the browser.
5
5
 
6
- ## One import, one line
6
+ ## Zero-config: the entire connection story
7
+
8
+ With only `URUN_API_KEY` in the server env (raw `pnpm dev` / `pnpm build`,
9
+ no CLI), this is a complete uRun frontend:
10
+
11
+ ```ts
12
+ // app/api/urun-token/route.ts — the entire server leg
13
+ import { createTokenRoute } from '@urun-sh/next'
14
+ export const POST = createTokenRoute()
15
+ ```
16
+
17
+ ```tsx
18
+ // Client code: hardcode YOUR app name, mirroring the backend's App("...").
19
+ // Sessions bind by method call — no function-name strings.
20
+ <UrunProvider app="rolling-sink">…</UrunProvider>
21
+ // const app = useApp(); const session = app.rolling_sink()
22
+ ```
23
+
24
+ `<UrunProvider>` (from `@urun-sh/react`) POSTs `/api/urun-token` after mount;
25
+ the route answers `{ token, expiresAt, orgId, gatewayUrl }` — org derived from
26
+ the key, gateway the platform front door (never user configuration). Nothing
27
+ else is set anywhere: no org id, no gateway URL, no `NEXT_PUBLIC_*`.
28
+
29
+ `createTokenRoute()` mints for ANY caller by default (the public-demo
30
+ posture — a server-side warning reminds you once); pass
31
+ `createTokenRoute({ authorize })` to restrict minting to your authenticated
32
+ users. All the scoping options below apply to it as well.
33
+
34
+ ## App Router (explicit authorization)
7
35
 
8
36
  ```ts
9
37
  // app/api/urun-token/route.ts
10
38
  import { urunTokenRoute } from '@urun-sh/next'
11
- export const POST = urunTokenRoute()
39
+
40
+ export const POST = urunTokenRoute({
41
+ authorize: (req) => Boolean(getSessionUser(req)), // required: do not mint for anonymous callers
42
+ subject: (req) => getSessionUser(req)?.id, // stable per-end-user identity
43
+ })
12
44
  ```
13
45
 
14
- Set the `URUN_API_KEY` env secret and you are done: the route mints
15
- short-lived tokens the browser passes to the SDK through the existing `jwt` /
16
- `getAccessToken` auth options.
46
+ Set the `URUN_API_KEY` env secret and provide an `authorize` callback that
47
+ returns `true` only for an authenticated application user/session. The route
48
+ mints short-lived tokens the browser passes to the SDK through the existing
49
+ `jwt` / `getAccessToken` auth options. Requests are rejected with `401` before
50
+ minting when `authorize` is omitted or returns false.
17
51
 
18
52
  ## Scoping
19
53
 
20
54
  ```ts
21
55
  export const POST = urunTokenRoute({
56
+ authorize: (req) => Boolean(getSessionUser(req)),
22
57
  allowedFunctions: ['zimage/generate'], // only these app/functions
23
58
  allowedOrigins: ['https://app.example.com'], // only from these Origins
24
59
  maxSessionS: 900, // per-session length cap
25
60
  expiresIn: 120, // token start-window (max 3600)
26
- subject: (req) => getUserId(req), // stable per-end-user identity
61
+ subject: (req) => getSessionUser(req)?.id, // stable per-end-user identity
27
62
  })
28
63
  ```
29
64
 
@@ -40,9 +75,43 @@ cap (folded with the function's own `max_session_s`).
40
75
  ```ts
41
76
  // pages/api/urun-token.ts
42
77
  import { urunTokenPagesHandler } from '@urun-sh/next'
43
- export default urunTokenPagesHandler()
78
+
79
+ export default urunTokenPagesHandler({
80
+ authorize: (req) => Boolean(getSessionUserFromPagesReq(req)),
81
+ })
44
82
  ```
45
83
 
84
+ ## Session config — hardcode nothing
85
+
86
+ The other half of the on-ramp: `urun deploy` derives your frontend's
87
+ session-plane config from the deploy (gateway from your login, app id +
88
+ function from the deployed app) and writes it to `frontend/.env.local`.
89
+ `urunSessionConfig()` reads it back so the frontend hardcodes **nothing**:
90
+
91
+ ```ts
92
+ // app/page.tsx — a Server Component
93
+ import { urunSessionConfig } from '@urun-sh/next'
94
+ import { Client } from './client'
95
+
96
+ export default function Page() {
97
+ const { baseUrl, appId, functionName } = urunSessionConfig()
98
+ return <Client baseUrl={baseUrl} appId={appId} functionName={functionName} />
99
+ }
100
+ ```
101
+
102
+ The **org id and auth provider are never here** — a frontend never hardcodes
103
+ its org. They are derived server-side from `URUN_API_KEY` when the token route
104
+ mints the token: the minted token carries the org claim, and `createClientToken`
105
+ returns the resolved `orgId` (in the `{ token, expiresAt, orgId }` response) for
106
+ the browser to hand to `<UrunProvider orgId=… jwt=… />`. The gateway ignores the
107
+ auth provider for platform-minted client tokens, so no `authProvider` is needed.
108
+
109
+ The end state: a user sets only `URUN_API_KEY` and runs `urun deploy` — no
110
+ `NEXT_PUBLIC_SESSION_*` editing.
111
+
112
+ Canonical env vars (plain server env, not `NEXT_PUBLIC_*`): `URUN_GATEWAY_URL`
113
+ (default `https://api.urun.sh`), `URUN_APP`, `URUN_FUNCTION`.
114
+
46
115
  ## Server SDK
47
116
 
48
117
  The underlying mint call is `createClientToken(apiKey, options)` from
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var l=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var b=(e,n)=>{for(var r in n)l(e,r,{get:n[r],enumerable:!0})},R=(e,n,r,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of k(n))!x.call(e,t)&&t!==r&&l(e,t,{get:()=>n[t],enumerable:!(o=y(n,t))||o.enumerable});return e};var S=e=>R(l({},"__esModule",{value:!0}),e);var P={};b(P,{URUN_API_KEY_ENV:()=>w,urunTokenPagesHandler:()=>U,urunTokenRoute:()=>O});module.exports=S(P);var d=require("@urun-sh/core"),w="URUN_API_KEY",I=60,a=class extends Error{};function h(e){return Array.isArray(e)&&e.every(n=>typeof n=="string")}function T(e,n){let r=n&&typeof n=="object"&&!Array.isArray(n)?n:{},o={expiresIn:e.expiresIn,allowedFunctions:e.allowedFunctions,allowedOrigins:e.allowedOrigins,maxSessionS:e.maxSessionS},t=r.expiresIn;if(t!==void 0){if(typeof t!="number"||!Number.isInteger(t)||t<1)throw new a("expiresIn must be a positive integer");let u=e.expiresIn??I;if(t>u)throw new a(`expiresIn may not exceed the configured ${u}s`);o.expiresIn=t}let s=(u,c,f)=>{if(u===void 0)return c;if(!h(u))throw new a(`${f} must be an array of strings`);if(c){let g=u.filter(p=>!c.includes(p));if(g.length>0)throw new a(`${f} may only narrow the configured allowlist (rejected: ${g.join(", ")})`)}return u};o.allowedFunctions=s(r.allowedFunctions,e.allowedFunctions,"allowedFunctions"),o.allowedOrigins=s(r.allowedOrigins,e.allowedOrigins,"allowedOrigins");let i=r.maxSessionS;if(i!==void 0){if(typeof i!="number"||!Number.isInteger(i)||i<1)throw new a("maxSessionS must be a positive integer");o.maxSessionS=e.maxSessionS===void 0?i:Math.min(e.maxSessionS,i)}return o}async function m(e,n,r){let o=e.apiKey??process.env[w];if(!o)return console.error(`[urun] token route: no API key \u2014 set ${w} or pass options.apiKey`),{status:500,body:{message:"token route not configured"}};let t;try{t=T(e,n)}catch(s){if(s instanceof a)return{status:400,body:{message:s.message}};throw s}try{let s=await(0,d.createClientToken)(o,{baseUrl:e.baseUrl,expiresIn:t.expiresIn,allowedFunctions:t.allowedFunctions,allowedOrigins:t.allowedOrigins,maxSessionS:t.maxSessionS,metadata:e.metadata,subject:r,fetch:e.fetch});return{status:200,body:{token:s.token,expiresAt:s.expiresAt.toISOString(),orgId:s.orgId}}}catch(s){let i=s instanceof d.ClientTokenError?`${s.message} (status ${s.status??"n/a"})`:s instanceof Error?s.message:String(s);return console.error(`[urun] token route: mint failed: ${i}`),{status:502,body:{message:"token mint failed"}}}}function O(e={}){return async n=>{let r;try{r=await n.json()}catch{r=void 0}let o=e.subject?await e.subject(n):void 0,{status:t,body:s}=await m(e,r,o);return new Response(JSON.stringify(s),{status:t,headers:{"content-type":"application/json"}})}}function U(e={}){return async(n,r)=>{if((n.method??"GET").toUpperCase()!=="POST"){r.status(405).json({message:"method not allowed"});return}let{status:o,body:t}=await m(e,n.body,void 0);r.status(o).json(t)}}0&&(module.exports={URUN_API_KEY_ENV,urunTokenPagesHandler,urunTokenRoute});
1
+ "use strict";var f=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var S=(e,n)=>{for(var o in n)f(e,o,{get:n[o],enumerable:!0})},O=(e,n,o,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of x(n))!T.call(e,t)&&t!==o&&f(e,t,{get:()=>n[t],enumerable:!(r=h(n,t))||r.enumerable});return e};var N=e=>O(f({},"__esModule",{value:!0}),e);var q={};S(q,{URUN_API_KEY_ENV:()=>l,URUN_APP_ENV:()=>m,URUN_FUNCTION_ENV:()=>k,URUN_GATEWAY_URL_ENV:()=>g,createTokenRoute:()=>C,urunSessionConfig:()=>I,urunTokenPagesHandler:()=>E,urunTokenRoute:()=>_});module.exports=N(q);var d=require("@urun-sh/core"),l="URUN_API_KEY",g="URUN_GATEWAY_URL",m="URUN_APP",k="URUN_FUNCTION";function I(e=process.env){return{baseUrl:e[g]?.trim()||d.DEFAULT_GATEWAY_URL,appId:e[m]?.trim()||void 0,functionName:e[k]?.trim()||void 0}}var P=60,u=class extends Error{};function A(e){return Array.isArray(e)&&e.every(n=>typeof n=="string")}function j(e,n){let o=n&&typeof n=="object"&&!Array.isArray(n)?n:{},r={expiresIn:e.expiresIn,allowedFunctions:e.allowedFunctions,allowedOrigins:e.allowedOrigins,maxSessionS:e.maxSessionS},t=o.expiresIn;if(t!==void 0){if(typeof t!="number"||!Number.isInteger(t)||t<1)throw new u("expiresIn must be a positive integer");let a=e.expiresIn??P;if(t>a)throw new u(`expiresIn may not exceed the configured ${a}s`);r.expiresIn=t}let s=(a,c,w)=>{if(a===void 0)return c;if(!A(a))throw new u(`${w} must be an array of strings`);if(c){let y=a.filter(b=>!c.includes(b));if(y.length>0)throw new u(`${w} may only narrow the configured allowlist (rejected: ${y.join(", ")})`)}return a};r.allowedFunctions=s(o.allowedFunctions,e.allowedFunctions,"allowedFunctions"),r.allowedOrigins=s(o.allowedOrigins,e.allowedOrigins,"allowedOrigins");let i=o.maxSessionS;if(i!==void 0){if(typeof i!="number"||!Number.isInteger(i)||i<1)throw new u("maxSessionS must be a positive integer");r.maxSessionS=e.maxSessionS===void 0?i:Math.min(e.maxSessionS,i)}return r}async function U(e,n){return e.authorize?await e.authorize(n)?void 0:{status:401,body:{message:"unauthorized"}}:(console.error("[urun] token route: options.authorize is required before minting"),{status:401,body:{message:"unauthorized"}})}async function p(e,n,o){let r=e.apiKey??process.env[l];if(!r)return console.error(`[urun] token route: no API key \u2014 set ${l} or pass options.apiKey`),{status:500,body:{message:"token route not configured"}};let t;try{t=j(e,n)}catch(s){if(s instanceof u)return{status:400,body:{message:s.message}};throw s}try{let s=await(0,d.createClientToken)(r,{baseUrl:e.baseUrl,expiresIn:t.expiresIn,allowedFunctions:t.allowedFunctions,allowedOrigins:t.allowedOrigins,maxSessionS:t.maxSessionS,metadata:e.metadata,subject:o,fetch:e.fetch});return{status:200,body:{token:s.token,expiresAt:s.expiresAt.toISOString(),orgId:s.orgId}}}catch(s){let i=s instanceof d.ClientTokenError?`${s.message} (status ${s.status??"n/a"})`:s instanceof Error?s.message:String(s);return console.error(`[urun] token route: mint failed: ${i}`),{status:502,body:{message:"token mint failed"}}}}function _(e){return async n=>{let o;try{o=await n.json()}catch{o=void 0}let r=await U(e,n);if(r)return new Response(JSON.stringify(r.body),{status:r.status,headers:{"content-type":"application/json"}});let t=e.subject?await e.subject(n):void 0,{status:s,body:i}=await p(e,o,t);return new Response(JSON.stringify(i),{status:s,headers:{"content-type":"application/json"}})}}function z(){return(process.env[g]?.trim()||d.DEFAULT_GATEWAY_URL).replace(/\/+$/,"")}var R=!1;function C(e={}){return async n=>{let o;try{o=await n.json()}catch{o=void 0}if(e.authorize){if(!await e.authorize(n))return new Response(JSON.stringify({message:"unauthorized"}),{status:401,headers:{"content-type":"application/json"}})}else R||(R=!0,console.warn("[urun] createTokenRoute: minting for ANY caller \u2014 pass { authorize } to restrict the endpoint to your authenticated users"));let r=e.subject?await e.subject(n):void 0,t=z(),{status:s,body:i}=await p({...e,baseUrl:t},o,r);if(s!==200)return new Response(JSON.stringify(i),{status:s,headers:{"content-type":"application/json"}});let a={...i,gatewayUrl:t};return new Response(JSON.stringify(a),{status:200,headers:{"content-type":"application/json"}})}}function E(e){return async(n,o)=>{if((n.method??"GET").toUpperCase()!=="POST"){o.status(405).json({message:"method not allowed"});return}let r=await U(e,n);if(r){o.status(r.status).json(r.body);return}let{status:t,body:s}=await p(e,n.body,void 0);o.status(t).json(s)}}0&&(module.exports={URUN_API_KEY_ENV,URUN_APP_ENV,URUN_FUNCTION_ENV,URUN_GATEWAY_URL_ENV,createTokenRoute,urunSessionConfig,urunTokenPagesHandler,urunTokenRoute});
package/dist/index.d.cts CHANGED
@@ -3,11 +3,8 @@ import { CreateClientTokenOptions } from '@urun-sh/core';
3
3
  /**
4
4
  * @urun-sh/next — the Next.js on-ramp for uRun scoped client tokens.
5
5
  *
6
- * The whole integration is ONE import + ONE line:
7
- *
8
- * // app/api/urun-token/route.ts
9
- * import { urunTokenRoute } from '@urun-sh/next'
10
- * export const POST = urunTokenRoute()
6
+ * The integration requires an application authorization callback so the
7
+ * token endpoint is never public by default.
11
8
  *
12
9
  * The route mints short-lived scoped client tokens with your org API key
13
10
  * (the standard `URUN_API_KEY` env secret) via `createClientToken` from
@@ -21,13 +18,60 @@ import { CreateClientTokenOptions } from '@urun-sh/core';
21
18
  * never widen anything, and it can never choose its own `subject` (identity
22
19
  * is server-derived via `options.subject`, defaulting to a random subject per
23
20
  * token so distinct end-users never share a session).
21
+ *
22
+ * SECURITY MODEL of the route itself: `options.authorize` is required and
23
+ * must return true before the route mints a token.
24
24
  */
25
25
 
26
26
  /** The standard env secret the route reads the org API key from. */
27
27
  declare const URUN_API_KEY_ENV = "URUN_API_KEY";
28
+ /**
29
+ * Canonical env var names carrying a frontend's session-plane config — the
30
+ * values `urun deploy` writes into `frontend/.env.local` and `urun dev`
31
+ * injects into the frontend dev server. Plain server env (NOT `NEXT_PUBLIC_*`):
32
+ * read them in a Server Component / route handler and pass them to your client
33
+ * provider as props.
34
+ */
35
+ declare const URUN_GATEWAY_URL_ENV = "URUN_GATEWAY_URL";
36
+ declare const URUN_APP_ENV = "URUN_APP";
37
+ declare const URUN_FUNCTION_ENV = "URUN_FUNCTION";
38
+ /** The frontend's resolved session-plane config (see {@link urunSessionConfig}). */
39
+ interface UrunSessionConfig {
40
+ /** Session gateway base URL — `URUN_GATEWAY_URL`, default the public gateway. */
41
+ baseUrl: string;
42
+ /** Deployed app id — `URUN_APP`; undefined until `urun deploy` emits it. */
43
+ appId?: string;
44
+ /** Session function name — `URUN_FUNCTION`; undefined until emitted. */
45
+ functionName?: string;
46
+ }
47
+ /**
48
+ * Read a frontend's session-plane config from the environment.
49
+ *
50
+ * This is the READER half of the CLI on-ramp: `urun deploy` derives these
51
+ * values from your deploy (gateway from your login, app id + function from the
52
+ * deployed app) and writes them to `frontend/.env.local`; this reads them back
53
+ * so the frontend hardcodes nothing.
54
+ *
55
+ * The org id and auth provider are DELIBERATELY absent — a frontend never
56
+ * hardcodes its org. They are derived SERVER-SIDE from `URUN_API_KEY` when the
57
+ * token route mints the client token: the minted token already carries the org
58
+ * claim, and {@link createClientToken} returns the resolved `orgId` for the
59
+ * browser to hand to the provider. The gateway ignores the auth provider for
60
+ * these platform-minted client tokens (trust = JWKS verify + org-claim match),
61
+ * so the provider's default is correct and no `authProvider` need be set.
62
+ *
63
+ * Read this SERVER-SIDE (plain env, not `NEXT_PUBLIC_*`) and pass the values
64
+ * to your client provider as props.
65
+ */
66
+ declare function urunSessionConfig(env?: Record<string, string | undefined>): UrunSessionConfig;
28
67
  interface UrunTokenRouteOptions extends Omit<CreateClientTokenOptions, 'fetch' | 'subject'> {
29
68
  /** Org API key (default: `process.env.URUN_API_KEY`). */
30
69
  apiKey?: string;
70
+ /**
71
+ * Authorize the caller before minting. Return true only for an
72
+ * authenticated application user/session that may receive a client token.
73
+ */
74
+ authorize: (request: Request | UrunTokenPagesRequest) => boolean | Promise<boolean>;
31
75
  /**
32
76
  * Derive a STABLE per-end-user subject from the request (e.g. your auth
33
77
  * session). A stable subject coalesces one user's tabs into one session;
@@ -38,17 +82,6 @@ interface UrunTokenRouteOptions extends Omit<CreateClientTokenOptions, 'fetch' |
38
82
  /** Fetch override (tests / custom agents). */
39
83
  fetch?: typeof fetch;
40
84
  }
41
- /** The JSON shape a successful token response carries to the browser. */
42
- interface UrunTokenResponseBody {
43
- token: string;
44
- expiresAt: string;
45
- orgId: string;
46
- }
47
- /**
48
- * A fully-built Next.js App Router route handler that mints scoped client
49
- * tokens: `export const POST = urunTokenRoute()`.
50
- */
51
- declare function urunTokenRoute(options?: UrunTokenRouteOptions): (request: Request) => Promise<Response>;
52
85
  /**
53
86
  * Structural Pages Router types — no dependency on `next` itself, so the
54
87
  * package builds/tests standalone and works with any Next version.
@@ -56,16 +89,73 @@ declare function urunTokenRoute(options?: UrunTokenRouteOptions): (request: Requ
56
89
  interface UrunTokenPagesRequest {
57
90
  method?: string;
58
91
  body?: unknown;
92
+ headers?: Record<string, string | string[] | undefined>;
59
93
  }
60
94
  interface UrunTokenPagesResponse {
61
95
  status(code: number): UrunTokenPagesResponse;
62
96
  json(body: unknown): void;
63
97
  }
98
+ /** The JSON shape a successful token response carries to the browser. */
99
+ interface UrunTokenResponseBody {
100
+ token: string;
101
+ expiresAt: string;
102
+ orgId: string;
103
+ }
104
+ /**
105
+ * A fully-built Next.js App Router route handler that mints scoped client
106
+ * tokens after the required authorization callback approves the caller.
107
+ */
108
+ declare function urunTokenRoute(options: UrunTokenRouteOptions): (request: Request) => Promise<Response>;
109
+ interface CreateTokenRouteOptions extends Omit<UrunTokenRouteOptions, 'authorize' | 'baseUrl'> {
110
+ /**
111
+ * OPTIONAL here (unlike {@link urunTokenRoute}, where it is required): the
112
+ * zero-config on-ramp exists precisely for public demo frontends where an
113
+ * anonymous browser legitimately receives a scoped short-lived token. When
114
+ * omitted, every caller mints (a server-side warning is logged once);
115
+ * provide it to restrict minting to your authenticated users.
116
+ */
117
+ authorize?: UrunTokenRouteOptions['authorize'];
118
+ }
119
+ /**
120
+ * The zero-config response: everything the API KEY implies, and nothing more.
121
+ * The app identity is deliberately ABSENT — the frontend hardcodes its app
122
+ * name (`<UrunProvider app="rolling-sink">`) exactly like the backend's
123
+ * `App("rolling-sink")`. Owner ruling 2026-07-15: no app introspection, no
124
+ * inference — an app author knows their own app.
125
+ */
126
+ interface CreateTokenRouteResponseBody extends UrunTokenResponseBody {
127
+ /**
128
+ * The platform endpoint the browser should connect to. NOT user
129
+ * configuration: the package hardcodes the public gateway, and the browser
130
+ * merely receives the server's resolution (owner ruling 2026-07-15: the
131
+ * gateway IS the platform front door, not something to discover or
132
+ * configure — users never see or set it).
133
+ */
134
+ gatewayUrl: string;
135
+ }
136
+ /**
137
+ * The ENTIRE server leg of a uRun frontend, zero-config:
138
+ *
139
+ * ```ts
140
+ * // app/api/urun-token/route.ts
141
+ * export const POST = createTokenRoute()
142
+ * ```
143
+ *
144
+ * With only `URUN_API_KEY` in the server env (raw `pnpm dev` / `pnpm build`,
145
+ * no urun CLI), the route mints a scoped client token and answers
146
+ * `{ token, expiresAt, orgId, gatewayUrl }` — org derived from the key,
147
+ * gateway the hardcoded platform front door (internal dev-cluster override
148
+ * via the buried `URUN_GATEWAY_URL` server env, never user config). The
149
+ * browser side is `<UrunProvider app="your-app">` — the app name is
150
+ * HARDCODED in app code, mirroring the backend's `App("your-app")` (owner
151
+ * ruling: no introspection, no inference). The API key never ships to the
152
+ * browser.
153
+ */
154
+ declare function createTokenRoute(options?: CreateTokenRouteOptions): (request: Request) => Promise<Response>;
64
155
  /**
65
- * The Pages Router variant: `export default urunTokenPagesHandler()`.
66
- * NOTE: `options.subject` receives no Request here (Pages req is structural);
67
- * derive identity inside your own wrapper if you need it on Pages Router.
156
+ * The Pages Router variant. Wrap this handler if Pages-specific subject
157
+ * derivation is needed; authorization receives the structural Pages request.
68
158
  */
69
- declare function urunTokenPagesHandler(options?: UrunTokenRouteOptions): (req: UrunTokenPagesRequest, res: UrunTokenPagesResponse) => Promise<void>;
159
+ declare function urunTokenPagesHandler(options: UrunTokenRouteOptions): (req: UrunTokenPagesRequest, res: UrunTokenPagesResponse) => Promise<void>;
70
160
 
71
- export { URUN_API_KEY_ENV, type UrunTokenPagesRequest, type UrunTokenPagesResponse, type UrunTokenResponseBody, type UrunTokenRouteOptions, urunTokenPagesHandler, urunTokenRoute };
161
+ export { type CreateTokenRouteOptions, type CreateTokenRouteResponseBody, URUN_API_KEY_ENV, URUN_APP_ENV, URUN_FUNCTION_ENV, URUN_GATEWAY_URL_ENV, type UrunSessionConfig, type UrunTokenPagesRequest, type UrunTokenPagesResponse, type UrunTokenResponseBody, type UrunTokenRouteOptions, createTokenRoute, urunSessionConfig, urunTokenPagesHandler, urunTokenRoute };
package/dist/index.d.ts CHANGED
@@ -1,32 +1,61 @@
1
1
  import { CreateClientTokenOptions } from '@urun-sh/core';
2
2
 
3
3
  declare const URUN_API_KEY_ENV = "URUN_API_KEY";
4
+
5
+ declare const URUN_GATEWAY_URL_ENV = "URUN_GATEWAY_URL";
6
+ declare const URUN_APP_ENV = "URUN_APP";
7
+ declare const URUN_FUNCTION_ENV = "URUN_FUNCTION";
8
+
9
+ interface UrunSessionConfig {
10
+
11
+ baseUrl: string;
12
+
13
+ appId?: string;
14
+
15
+ functionName?: string;
16
+ }
17
+
18
+ declare function urunSessionConfig(env?: Record<string, string | undefined>): UrunSessionConfig;
4
19
  interface UrunTokenRouteOptions extends Omit<CreateClientTokenOptions, 'fetch' | 'subject'> {
5
20
 
6
21
  apiKey?: string;
7
22
 
23
+ authorize: (request: Request | UrunTokenPagesRequest) => boolean | Promise<boolean>;
24
+
8
25
  subject?: (request: Request) => string | undefined | Promise<string | undefined>;
9
26
 
10
27
  fetch?: typeof fetch;
11
28
  }
12
29
 
13
- interface UrunTokenResponseBody {
14
- token: string;
15
- expiresAt: string;
16
- orgId: string;
17
- }
18
-
19
- declare function urunTokenRoute(options?: UrunTokenRouteOptions): (request: Request) => Promise<Response>;
20
-
21
30
  interface UrunTokenPagesRequest {
22
31
  method?: string;
23
32
  body?: unknown;
33
+ headers?: Record<string, string | string[] | undefined>;
24
34
  }
25
35
  interface UrunTokenPagesResponse {
26
36
  status(code: number): UrunTokenPagesResponse;
27
37
  json(body: unknown): void;
28
38
  }
29
39
 
30
- declare function urunTokenPagesHandler(options?: UrunTokenRouteOptions): (req: UrunTokenPagesRequest, res: UrunTokenPagesResponse) => Promise<void>;
40
+ interface UrunTokenResponseBody {
41
+ token: string;
42
+ expiresAt: string;
43
+ orgId: string;
44
+ }
45
+
46
+ declare function urunTokenRoute(options: UrunTokenRouteOptions): (request: Request) => Promise<Response>;
47
+ interface CreateTokenRouteOptions extends Omit<UrunTokenRouteOptions, 'authorize' | 'baseUrl'> {
48
+
49
+ authorize?: UrunTokenRouteOptions['authorize'];
50
+ }
51
+
52
+ interface CreateTokenRouteResponseBody extends UrunTokenResponseBody {
53
+
54
+ gatewayUrl: string;
55
+ }
56
+
57
+ declare function createTokenRoute(options?: CreateTokenRouteOptions): (request: Request) => Promise<Response>;
58
+
59
+ declare function urunTokenPagesHandler(options: UrunTokenRouteOptions): (req: UrunTokenPagesRequest, res: UrunTokenPagesResponse) => Promise<void>;
31
60
 
32
- export { URUN_API_KEY_ENV, type UrunTokenPagesRequest, type UrunTokenPagesResponse, type UrunTokenResponseBody, type UrunTokenRouteOptions, urunTokenPagesHandler, urunTokenRoute };
61
+ export { type CreateTokenRouteOptions, type CreateTokenRouteResponseBody, URUN_API_KEY_ENV, URUN_APP_ENV, URUN_FUNCTION_ENV, URUN_GATEWAY_URL_ENV, type UrunSessionConfig, type UrunTokenPagesRequest, type UrunTokenPagesResponse, type UrunTokenResponseBody, type UrunTokenRouteOptions, createTokenRoute, urunSessionConfig, urunTokenPagesHandler, urunTokenRoute };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{createClientToken as m,ClientTokenError as p}from"@urun-sh/core";var w="URUN_API_KEY",y=60,a=class extends Error{};function k(e){return Array.isArray(e)&&e.every(s=>typeof s=="string")}function x(e,s){let r=s&&typeof s=="object"&&!Array.isArray(s)?s:{},o={expiresIn:e.expiresIn,allowedFunctions:e.allowedFunctions,allowedOrigins:e.allowedOrigins,maxSessionS:e.maxSessionS},t=r.expiresIn;if(t!==void 0){if(typeof t!="number"||!Number.isInteger(t)||t<1)throw new a("expiresIn must be a positive integer");let u=e.expiresIn??y;if(t>u)throw new a(`expiresIn may not exceed the configured ${u}s`);o.expiresIn=t}let n=(u,d,c)=>{if(u===void 0)return d;if(!k(u))throw new a(`${c} must be an array of strings`);if(d){let l=u.filter(g=>!d.includes(g));if(l.length>0)throw new a(`${c} may only narrow the configured allowlist (rejected: ${l.join(", ")})`)}return u};o.allowedFunctions=n(r.allowedFunctions,e.allowedFunctions,"allowedFunctions"),o.allowedOrigins=n(r.allowedOrigins,e.allowedOrigins,"allowedOrigins");let i=r.maxSessionS;if(i!==void 0){if(typeof i!="number"||!Number.isInteger(i)||i<1)throw new a("maxSessionS must be a positive integer");o.maxSessionS=e.maxSessionS===void 0?i:Math.min(e.maxSessionS,i)}return o}async function f(e,s,r){let o=e.apiKey??process.env[w];if(!o)return console.error(`[urun] token route: no API key \u2014 set ${w} or pass options.apiKey`),{status:500,body:{message:"token route not configured"}};let t;try{t=x(e,s)}catch(n){if(n instanceof a)return{status:400,body:{message:n.message}};throw n}try{let n=await m(o,{baseUrl:e.baseUrl,expiresIn:t.expiresIn,allowedFunctions:t.allowedFunctions,allowedOrigins:t.allowedOrigins,maxSessionS:t.maxSessionS,metadata:e.metadata,subject:r,fetch:e.fetch});return{status:200,body:{token:n.token,expiresAt:n.expiresAt.toISOString(),orgId:n.orgId}}}catch(n){let i=n instanceof p?`${n.message} (status ${n.status??"n/a"})`:n instanceof Error?n.message:String(n);return console.error(`[urun] token route: mint failed: ${i}`),{status:502,body:{message:"token mint failed"}}}}function R(e={}){return async s=>{let r;try{r=await s.json()}catch{r=void 0}let o=e.subject?await e.subject(s):void 0,{status:t,body:n}=await f(e,r,o);return new Response(JSON.stringify(n),{status:t,headers:{"content-type":"application/json"}})}}function S(e={}){return async(s,r)=>{if((s.method??"GET").toUpperCase()!=="POST"){r.status(405).json({message:"method not allowed"});return}let{status:o,body:t}=await f(e,s.body,void 0);r.status(o).json(t)}}export{w as URUN_API_KEY_ENV,S as urunTokenPagesHandler,R as urunTokenRoute};
1
+ import{createClientToken as k,ClientTokenError as U,DEFAULT_GATEWAY_URL as w}from"@urun-sh/core";var g="URUN_API_KEY",y="URUN_GATEWAY_URL",b="URUN_APP",h="URUN_FUNCTION";function I(e=process.env){return{baseUrl:e[y]?.trim()||w,appId:e[b]?.trim()||void 0,functionName:e[h]?.trim()||void 0}}var x=60,u=class extends Error{};function T(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function S(e,t){let o=t&&typeof t=="object"&&!Array.isArray(t)?t:{},r={expiresIn:e.expiresIn,allowedFunctions:e.allowedFunctions,allowedOrigins:e.allowedOrigins,maxSessionS:e.maxSessionS},s=o.expiresIn;if(s!==void 0){if(typeof s!="number"||!Number.isInteger(s)||s<1)throw new u("expiresIn must be a positive integer");let a=e.expiresIn??x;if(s>a)throw new u(`expiresIn may not exceed the configured ${a}s`);r.expiresIn=s}let n=(a,d,f)=>{if(a===void 0)return d;if(!T(a))throw new u(`${f} must be an array of strings`);if(d){let l=a.filter(m=>!d.includes(m));if(l.length>0)throw new u(`${f} may only narrow the configured allowlist (rejected: ${l.join(", ")})`)}return a};r.allowedFunctions=n(o.allowedFunctions,e.allowedFunctions,"allowedFunctions"),r.allowedOrigins=n(o.allowedOrigins,e.allowedOrigins,"allowedOrigins");let i=o.maxSessionS;if(i!==void 0){if(typeof i!="number"||!Number.isInteger(i)||i<1)throw new u("maxSessionS must be a positive integer");r.maxSessionS=e.maxSessionS===void 0?i:Math.min(e.maxSessionS,i)}return r}async function R(e,t){return e.authorize?await e.authorize(t)?void 0:{status:401,body:{message:"unauthorized"}}:(console.error("[urun] token route: options.authorize is required before minting"),{status:401,body:{message:"unauthorized"}})}async function c(e,t,o){let r=e.apiKey??process.env[g];if(!r)return console.error(`[urun] token route: no API key \u2014 set ${g} or pass options.apiKey`),{status:500,body:{message:"token route not configured"}};let s;try{s=S(e,t)}catch(n){if(n instanceof u)return{status:400,body:{message:n.message}};throw n}try{let n=await k(r,{baseUrl:e.baseUrl,expiresIn:s.expiresIn,allowedFunctions:s.allowedFunctions,allowedOrigins:s.allowedOrigins,maxSessionS:s.maxSessionS,metadata:e.metadata,subject:o,fetch:e.fetch});return{status:200,body:{token:n.token,expiresAt:n.expiresAt.toISOString(),orgId:n.orgId}}}catch(n){let i=n instanceof U?`${n.message} (status ${n.status??"n/a"})`:n instanceof Error?n.message:String(n);return console.error(`[urun] token route: mint failed: ${i}`),{status:502,body:{message:"token mint failed"}}}}function P(e){return async t=>{let o;try{o=await t.json()}catch{o=void 0}let r=await R(e,t);if(r)return new Response(JSON.stringify(r.body),{status:r.status,headers:{"content-type":"application/json"}});let s=e.subject?await e.subject(t):void 0,{status:n,body:i}=await c(e,o,s);return new Response(JSON.stringify(i),{status:n,headers:{"content-type":"application/json"}})}}function O(){return(process.env[y]?.trim()||w).replace(/\/+$/,"")}var p=!1;function A(e={}){return async t=>{let o;try{o=await t.json()}catch{o=void 0}if(e.authorize){if(!await e.authorize(t))return new Response(JSON.stringify({message:"unauthorized"}),{status:401,headers:{"content-type":"application/json"}})}else p||(p=!0,console.warn("[urun] createTokenRoute: minting for ANY caller \u2014 pass { authorize } to restrict the endpoint to your authenticated users"));let r=e.subject?await e.subject(t):void 0,s=O(),{status:n,body:i}=await c({...e,baseUrl:s},o,r);if(n!==200)return new Response(JSON.stringify(i),{status:n,headers:{"content-type":"application/json"}});let a={...i,gatewayUrl:s};return new Response(JSON.stringify(a),{status:200,headers:{"content-type":"application/json"}})}}function j(e){return async(t,o)=>{if((t.method??"GET").toUpperCase()!=="POST"){o.status(405).json({message:"method not allowed"});return}let r=await R(e,t);if(r){o.status(r.status).json(r.body);return}let{status:s,body:n}=await c(e,t.body,void 0);o.status(s).json(n)}}export{g as URUN_API_KEY_ENV,b as URUN_APP_ENV,h as URUN_FUNCTION_ENV,y as URUN_GATEWAY_URL_ENV,A as createTokenRoute,I as urunSessionConfig,j as urunTokenPagesHandler,P as urunTokenRoute};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urun-sh/next",
3
- "version": "0.2.26",
3
+ "version": "0.2.29",
4
4
  "description": "Next.js on-ramp for uRun scoped client tokens — a fully-built token route in one import and one line.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,22 +31,23 @@
31
31
  "files": [
32
32
  "dist"
33
33
  ],
34
+ "scripts": {
35
+ "build": "tsup && node ../../scripts/sanitize-dist.mjs .",
36
+ "prepack": "pnpm build",
37
+ "test": "vitest run",
38
+ "dev": "tsup --watch"
39
+ },
34
40
  "peerDependencies": {
35
- "@urun-sh/core": "^0.2.26"
41
+ "@urun-sh/core": "^0.2.2"
36
42
  },
37
43
  "devDependencies": {
38
44
  "@types/node": "^24.0.0",
45
+ "@urun-sh/core": "workspace:*",
39
46
  "tsup": "^8.5.0",
40
47
  "typescript": "^5.3.0",
41
- "vitest": "^3.0.0",
42
- "@urun-sh/core": "0.2.26"
48
+ "vitest": "^3.0.0"
43
49
  },
44
50
  "publishConfig": {
45
51
  "access": "public"
46
- },
47
- "scripts": {
48
- "build": "tsup && node ../../scripts/sanitize-dist.mjs .",
49
- "test": "vitest run",
50
- "dev": "tsup --watch"
51
52
  }
52
- }
53
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 urun contributors
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.