@happ-cli/nextjs 0.1.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/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happ/nextjs — Next.js App Router adapter for the Happ feature entitlement platform.
|
|
3
|
+
*
|
|
4
|
+
* Extends @happ/sdk with gate() and withFeature() that return NextResponse,
|
|
5
|
+
* so route handlers stay clean and framework-idiomatic.
|
|
6
|
+
*
|
|
7
|
+
* Quickstart:
|
|
8
|
+
*
|
|
9
|
+
* // src/lib/happ.ts
|
|
10
|
+
* import { createHappClient } from "@happ/nextjs"
|
|
11
|
+
* export const happ = createHappClient() // reads HAPP_API_URL + HAPP_API_KEY from env
|
|
12
|
+
*
|
|
13
|
+
* // app/api/invoices/route.ts
|
|
14
|
+
* import { happ } from "@/lib/happ"
|
|
15
|
+
*
|
|
16
|
+
* export async function POST(req: Request) {
|
|
17
|
+
* const session = await auth()
|
|
18
|
+
* if (!session?.user?.id) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
|
19
|
+
*
|
|
20
|
+
* const { customerId, items } = await req.json()
|
|
21
|
+
*
|
|
22
|
+
* return happ.withFeature("invoices", session.user.id, async () => {
|
|
23
|
+
* const invoice = await prisma.invoice.create({ ... })
|
|
24
|
+
* return NextResponse.json(invoice)
|
|
25
|
+
* }, { metered: true })
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
import { NextResponse } from "next/server";
|
|
29
|
+
import { HappClient, HappClientOptions } from "@happ-cli/sdk";
|
|
30
|
+
/**
|
|
31
|
+
* Discriminated union returned by gate().
|
|
32
|
+
* TypeScript narrows the shape automatically after the allowed check.
|
|
33
|
+
*/
|
|
34
|
+
export type GateResult = {
|
|
35
|
+
allowed: true;
|
|
36
|
+
effectiveSubscriberId: string;
|
|
37
|
+
} | {
|
|
38
|
+
allowed: false;
|
|
39
|
+
response: NextResponse;
|
|
40
|
+
};
|
|
41
|
+
export declare class HappNextjsClient extends HappClient {
|
|
42
|
+
/**
|
|
43
|
+
* Evaluate a feature gate and return either the entitlement or a ready-made 403 response.
|
|
44
|
+
*
|
|
45
|
+
* const gate = await happ.gate("invoices", userId)
|
|
46
|
+
* if (!gate.allowed) return gate.response
|
|
47
|
+
* // gate.effectiveSubscriberId is available here
|
|
48
|
+
*/
|
|
49
|
+
gate(featureKey: string, subscriberId: string): Promise<GateResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Gate a feature, run an action, and (for metered features) auto-increment usage.
|
|
52
|
+
* The action only runs if the gate passes. Increment only fires on a successful response.
|
|
53
|
+
*
|
|
54
|
+
* // Boolean feature — no increment
|
|
55
|
+
* return happ.withFeature("duplicate-invoice", userId, async () => {
|
|
56
|
+
* return NextResponse.json(duplicate)
|
|
57
|
+
* })
|
|
58
|
+
*
|
|
59
|
+
* // Metered feature — auto-increments after success
|
|
60
|
+
* return happ.withFeature("invoices", userId, async () => {
|
|
61
|
+
* return NextResponse.json(invoice)
|
|
62
|
+
* }, { metered: true })
|
|
63
|
+
*/
|
|
64
|
+
withFeature(featureKey: string, subscriberId: string, fn: () => Promise<Response>, options?: {
|
|
65
|
+
metered?: boolean;
|
|
66
|
+
}): Promise<Response>;
|
|
67
|
+
}
|
|
68
|
+
/** Create a Happ Next.js client. Options default to HAPP_API_URL and HAPP_API_KEY env vars. */
|
|
69
|
+
export declare function createHappClient(options?: HappClientOptions): HappNextjsClient;
|
|
70
|
+
export type { HappClientOptions, EvalResult } from "@happ-cli/sdk";
|
|
71
|
+
export { DENIAL_MESSAGES } from "@happ-cli/sdk";
|
|
72
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAmB,MAAM,eAAe,CAAA;AAI9E;;;GAGG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAE,qBAAqB,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE,CAAA;AAI9C,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C;;;;;;OAMG;IACG,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAmBzE;;;;;;;;;;;;;OAaG;IACG,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,EAC3B,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,OAAO,CAAC,QAAQ,CAAC;CAYrB;AAED,+FAA+F;AAC/F,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,gBAAgB,CAE9E;AAGD,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happ/nextjs — Next.js App Router adapter for the Happ feature entitlement platform.
|
|
3
|
+
*
|
|
4
|
+
* Extends @happ/sdk with gate() and withFeature() that return NextResponse,
|
|
5
|
+
* so route handlers stay clean and framework-idiomatic.
|
|
6
|
+
*
|
|
7
|
+
* Quickstart:
|
|
8
|
+
*
|
|
9
|
+
* // src/lib/happ.ts
|
|
10
|
+
* import { createHappClient } from "@happ/nextjs"
|
|
11
|
+
* export const happ = createHappClient() // reads HAPP_API_URL + HAPP_API_KEY from env
|
|
12
|
+
*
|
|
13
|
+
* // app/api/invoices/route.ts
|
|
14
|
+
* import { happ } from "@/lib/happ"
|
|
15
|
+
*
|
|
16
|
+
* export async function POST(req: Request) {
|
|
17
|
+
* const session = await auth()
|
|
18
|
+
* if (!session?.user?.id) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
|
19
|
+
*
|
|
20
|
+
* const { customerId, items } = await req.json()
|
|
21
|
+
*
|
|
22
|
+
* return happ.withFeature("invoices", session.user.id, async () => {
|
|
23
|
+
* const invoice = await prisma.invoice.create({ ... })
|
|
24
|
+
* return NextResponse.json(invoice)
|
|
25
|
+
* }, { metered: true })
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
import { NextResponse } from "next/server";
|
|
29
|
+
import { HappClient, DENIAL_MESSAGES } from "@happ-cli/sdk";
|
|
30
|
+
// ─── Client ───────────────────────────────────────────────────────────────────
|
|
31
|
+
export class HappNextjsClient extends HappClient {
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate a feature gate and return either the entitlement or a ready-made 403 response.
|
|
34
|
+
*
|
|
35
|
+
* const gate = await happ.gate("invoices", userId)
|
|
36
|
+
* if (!gate.allowed) return gate.response
|
|
37
|
+
* // gate.effectiveSubscriberId is available here
|
|
38
|
+
*/
|
|
39
|
+
async gate(featureKey, subscriberId) {
|
|
40
|
+
const result = await this.eval(featureKey, subscriberId);
|
|
41
|
+
if (!result.allowed) {
|
|
42
|
+
const message = DENIAL_MESSAGES[result.reason ?? ""] ??
|
|
43
|
+
`Access denied (${result.reason ?? "unknown"})`;
|
|
44
|
+
return {
|
|
45
|
+
allowed: false,
|
|
46
|
+
response: NextResponse.json({ error: message }, { status: 403 }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
allowed: true,
|
|
51
|
+
effectiveSubscriberId: result.effectiveSubscriberId ?? subscriberId,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Gate a feature, run an action, and (for metered features) auto-increment usage.
|
|
56
|
+
* The action only runs if the gate passes. Increment only fires on a successful response.
|
|
57
|
+
*
|
|
58
|
+
* // Boolean feature — no increment
|
|
59
|
+
* return happ.withFeature("duplicate-invoice", userId, async () => {
|
|
60
|
+
* return NextResponse.json(duplicate)
|
|
61
|
+
* })
|
|
62
|
+
*
|
|
63
|
+
* // Metered feature — auto-increments after success
|
|
64
|
+
* return happ.withFeature("invoices", userId, async () => {
|
|
65
|
+
* return NextResponse.json(invoice)
|
|
66
|
+
* }, { metered: true })
|
|
67
|
+
*/
|
|
68
|
+
async withFeature(featureKey, subscriberId, fn, options = {}) {
|
|
69
|
+
const gate = await this.gate(featureKey, subscriberId);
|
|
70
|
+
if (!gate.allowed)
|
|
71
|
+
return gate.response;
|
|
72
|
+
const response = await fn();
|
|
73
|
+
if (options.metered && response.status < 400) {
|
|
74
|
+
await this.increment(featureKey, gate.effectiveSubscriberId);
|
|
75
|
+
}
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Create a Happ Next.js client. Options default to HAPP_API_URL and HAPP_API_KEY env vars. */
|
|
80
|
+
export function createHappClient(options) {
|
|
81
|
+
return new HappNextjsClient(options);
|
|
82
|
+
}
|
|
83
|
+
export { DENIAL_MESSAGES } from "@happ-cli/sdk";
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAqB,eAAe,EAAE,MAAM,eAAe,CAAA;AAY9E,iFAAiF;AAEjF,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAC9C;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CAAC,UAAkB,EAAE,YAAoB;QACjD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;QAExD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,OAAO,GACX,eAAe,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;gBACpC,kBAAkB,MAAM,CAAC,MAAM,IAAI,SAAS,GAAG,CAAA;YACjD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,QAAQ,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;aACjE,CAAA;QACH,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,qBAAqB,EAAE,MAAM,CAAC,qBAAqB,IAAI,YAAY;SACpE,CAAA;IACH,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,WAAW,CACf,UAAkB,EAClB,YAAoB,EACpB,EAA2B,EAC3B,UAAiC,EAAE;QAEnC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;QACtD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,QAAQ,CAAA;QAEvC,MAAM,QAAQ,GAAG,MAAM,EAAE,EAAE,CAAA;QAE3B,IAAI,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YAC7C,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAA;QAC9D,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF;AAED,+FAA+F;AAC/F,MAAM,UAAU,gBAAgB,CAAC,OAA2B;IAC1D,OAAO,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAA;AACtC,CAAC;AAID,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happ-cli/nextjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Happ feature entitlement SDK — Next.js App Router adapter",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "tsc --watch"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@happ-cli/sdk": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"next": ">=14"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.14.0",
|
|
37
|
+
"next": "^15.0.0",
|
|
38
|
+
"typescript": "^5.4.5"
|
|
39
|
+
}
|
|
40
|
+
}
|