@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.
@@ -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
+ }