@camada/nuxt 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 camada
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,158 @@
1
+ # @camada/nuxt
2
+
3
+ camada for [Nuxt](https://nuxt.com) as h3 server middleware: enforces the tenant snapshot inline
4
+ (your ordered custom rules, then block, allow, challenge), serves a first-party proof-of-work
5
+ challenge page and beacon, records the outcomes your server routes know (`track()`), and ships
6
+ one wire event per request with the status your app really answered. Fails open by design — a
7
+ camada outage or bug never 5xxes your app.
8
+
9
+ Not yet on npm — consumed via a `file:` dependency from a sibling checkout.
10
+
11
+ ## Quickstart
12
+
13
+ Two files under `server/`:
14
+
15
+ ```ts
16
+ // server/middleware/camada.ts
17
+ import { camada } from '@camada/nuxt';
18
+ export default camada(); // reads CAMADA_KEY / CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL from process.env
19
+ ```
20
+
21
+ ```ts
22
+ // server/plugins/camada.ts
23
+ import { camadaNitroPlugin } from '@camada/nuxt';
24
+ export default camadaNitroPlugin(); // puts the beacon tag in every rendered <head>
25
+ ```
26
+
27
+ Env (printed by camada onboarding / `npm run seed` in dev):
28
+
29
+ ```
30
+ CAMADA_KEY=<ingest_token>.<snap_token>
31
+ CAMADA_INGEST_URL=http://localhost:8787 # dev only; defaults to production ingest
32
+ ```
33
+
34
+ An app that reads its own config can pass the values instead:
35
+
36
+ ```ts
37
+ export default camada({ key: MY_KEY, ingestUrl: MY_INGEST });
38
+ ```
39
+
40
+ The middleware is built once per `camada()` call and keeps one engine per resolved
41
+ configuration. Without `CAMADA_KEY` it is inert (one log line, no requests, no enforcement),
42
+ so an unprovisioned environment behaves exactly as if camada were not installed.
43
+
44
+ ## What it does per request
45
+
46
+ 1. Refreshes the snapshot off-path (lazy mode by default: no interval timers, so the same
47
+ build runs on a Node server and on an edge preset). Every poll and event batch carries
48
+ `x-camada-sdk: @camada/nuxt/<version>`, and polls ask for snapshot v5 — the container that
49
+ carries your ordered custom rules.
50
+ 2. Resolves the client from the peer h3 vouches for — `getRequestIP(event)`: the socket
51
+ address, or the `clientAddress` a Nitro preset stamped on the context — combined with
52
+ `X-Forwarded-For` only under your tenant's trusted-proxy config. A bare header
53
+ (`x-forwarded-for`, `cf-connecting-ip`, `x-real-ip`) never becomes the client on its own.
54
+ 3. Runs your ordered custom rules, then the allow, block and challenge lists.
55
+ 4. **Block** → `403` with `x-block-reason` before your route; the event still ships, with
56
+ `st: 403` and `blk: <reason>` so the analyst counts SDK blocks apart from your own 403s.
57
+ 5. **Challenge** → a `403` proof-of-work page for HTML navigations, `403 {"error":
58
+ "challenge_required"}` for anything else; the solution posts to `/__camada/challenge`,
59
+ which sets `_cch` and 302s back.
60
+ 6. Otherwise the request falls through to your routes and pages. The event ships on the
61
+ response's `finish` with the real `statusCode` — a Node response, or the mock a web preset
62
+ provides; `st` is null only for an event without a response object at all. Your own
63
+ `readBody()` still sees the request body (the middleware hands h3 the stream it built), and
64
+ Nitro's internal `event.$fetch` / `useFetch` during SSR is left to the outer request (found on the
65
+ inner request's `__unenv__` context; a bare `$fetch` with no event is a request of its own): one page view
66
+ is one event.
67
+
68
+ A first visit is given the shared `_sfp` session cookie through h3's `setCookie` before your
69
+ handler runs (`HttpOnly; SameSite=Lax; Path=/; Max-Age=30d`, `Secure` when the request URL is
70
+ https), so a redirect from your own handler carries it too. An existing session is never
71
+ overwritten.
72
+
73
+ ## Options
74
+
75
+ | option | default | meaning |
76
+ |---|---|---|
77
+ | `key` | `env.CAMADA_KEY` | `<ingest_token>.<snap_token>`; without it the middleware is inert |
78
+ | `ingestUrl` | `env.CAMADA_INGEST_URL` | ingest base; batches go to `<ingestUrl>/e` |
79
+ | `snapshotUrl` | `<ingestUrl>/snapshot` | snapshot endpoint |
80
+ | `trustedProxy` | server config | `none` / `vercel` / `hops:N` / `cidrs:a,b`, or the parsed object |
81
+ | `challenge` | `true` | serve the proof-of-work page for `challenge` verdicts |
82
+ | `challengePath` | `/__camada/challenge` | where that page posts its solution |
83
+ | `snapshotVersion` | `5` | `4` drops the custom rules, `3` the allow/challenge sides too |
84
+ | `scriptPath` | `/_cam/b.js` | where the first-party beacon script is served |
85
+ | `fpPath` | `/_cam/fp` | where that script posts the beacon; keep it in `scriptPath`'s directory |
86
+ | `mode` | `lazy` (or `timer`) | `timer` polls the snapshot on an unref'd interval (long-lived process); `lazy` refreshes it per request off-path. `CAMADA_SERVERLESS=1` forces `lazy` |
87
+ | `env` | `process.env` | overrides the process env (tests, and apps that read config themselves) |
88
+
89
+ `CAMADA_CHALLENGE=0` switches the challenge off without a code change.
90
+
91
+ `CAMADA_DISABLED=1` switches everything off, checked per request.
92
+
93
+ ## The first-party beacon
94
+
95
+ Bots that never run JavaScript are the cheapest to catch. The Nitro plugin above pushes the tag
96
+ into the `<head>` Nuxt renders; a server route that writes its own HTML uses the helper:
97
+
98
+ ```ts
99
+ import { scriptTag } from '@camada/nuxt';
100
+
101
+ export default defineEventHandler((event) => `<html><head>${scriptTag(event)}</head><body>…</body></html>`);
102
+ ```
103
+
104
+ `scriptTag(event)` returns `<script src="/_cam/b.js?r=<rid>" async></script>` — the `rid` is this
105
+ request's event id, so the analyst joins the beacon to the page view. The middleware serves the
106
+ script at `GET /_cam/b.js` (cacheable, 1 h) and relays `POST /_cam/fp` (≤ 32 KB, answers 204)
107
+ onto the event batch as a `sig: 1` row stamped with the client ip camada resolved — never the
108
+ one the body claims. Both endpoints sit behind the verdict: a blocked client gets 403 there
109
+ too. The tag is `''` where the middleware did not run or the project turned the beacon off in
110
+ its settings, and the endpoints stand down with it.
111
+
112
+ ## App-context events
113
+
114
+ The wire shows a `POST /api/login`; only your route knows whether it failed. Tell camada:
115
+
116
+ ```ts
117
+ // server/api/login.post.ts
118
+ import { track } from '@camada/nuxt';
119
+
120
+ export default defineEventHandler(async (event) => {
121
+ const { email, password } = await readBody(event);
122
+ const ok = await signIn(email, password);
123
+ if (!ok) track(event, 'login_failed', { user: email }); // await optional
124
+ setResponseStatus(event, ok ? 200 : 401);
125
+ return ok ? { ok } : 'Invalid credentials';
126
+ });
127
+ ```
128
+
129
+ `track(event, name, { user? })` ships `{ et, uid, rid, sid, ip, ts }` joined to this request's
130
+ event. The user identifier is HMAC-hashed in-process with the ingest token — the raw value never
131
+ leaves the process. It never throws and is a no-op where the middleware did not run. The event
132
+ name is free-form; the analyst's rules read this vocabulary:
133
+
134
+ | event | when |
135
+ |---|---|
136
+ | `login_failed` / `login_succeeded` | a credential check settled |
137
+ | `signup` | an account was created |
138
+ | `password_reset` | a reset was requested |
139
+ | `mfa_failed` | a second factor was rejected |
140
+ | `payment_failed` / `payment_succeeded` | a charge settled |
141
+ | `coupon_failed` | a promo code was rejected |
142
+
143
+ ## What this tap can see
144
+
145
+ This is the in-app position: the beacon, the client hints the browser sends, the status your
146
+ app answered, the session, and the outcomes your routes report. h3 vouches for the peer
147
+ address (the socket, or a preset's `clientAddress`) and for nothing about the connection
148
+ beyond it: no ASN, country or TLS fingerprint, so rules on those conditions do not enforce here
149
+ (the analyst knows that from the tap's capability mask, `sdk-nuxt`, and never scores their
150
+ absence as evidence). The protocol on the event is `null` — h3 does not see the client's own —
151
+ and the one forwarded header h3 does honour is `x-forwarded-proto`, which decides only whether
152
+ the session cookie is marked `Secure`. Node normalises header order, so the raw-wire-order
153
+ signal is not available either.
154
+
155
+ ## Fail open
156
+
157
+ Every entry point runs inside camada's guard. A dead ingest, a corrupt snapshot, a bug in this
158
+ package: telemetry is lost, the request is not.
package/dist/index.cjs ADDED
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ camada: () => camada,
34
+ camadaNitroPlugin: () => camadaNitroPlugin,
35
+ resetCamada: () => resetCamada,
36
+ scriptTag: () => scriptTag,
37
+ track: () => track
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/camada.ts
42
+ var import_h3 = require("h3");
43
+ var import_core = require("@camada/core");
44
+ var import_fetch = require("@camada/core/fetch");
45
+ var import_iife_string = __toESM(require("@camada/browser/iife-string"), 1);
46
+
47
+ // package.json
48
+ var name = "@camada/nuxt";
49
+ var version = "0.1.0";
50
+
51
+ // src/version.ts
52
+ var SDK_ID = `${name}/${version}`;
53
+
54
+ // src/camada.ts
55
+ var VAR = "__camada";
56
+ var instances = /* @__PURE__ */ new Set();
57
+ var slotOf = (event) => (0, import_core.guarded)(() => {
58
+ const own = event.context[VAR];
59
+ if (own) return own;
60
+ const outer = event.node?.req?.__unenv__;
61
+ return outer?.[VAR];
62
+ }, void 0);
63
+ function camada(opts = {}) {
64
+ const cam = (0, import_fetch.createFetchCamada)({ tap: import_core.TAP_NUXT, sdk: SDK_ID, iife: import_iife_string.default }, opts);
65
+ instances.add(cam);
66
+ return (0, import_h3.defineEventHandler)(async (event) => {
67
+ if (slotOf(event)) return;
68
+ const req = (0, import_core.guarded)(() => (0, import_h3.toWebRequest)(event), null);
69
+ if (!req) return;
70
+ if (req.body) event._requestBody = req.body;
71
+ const ctx = {
72
+ // The socket address (or what the host preset stamped as clientAddress); never
73
+ // X-Forwarded-For here, core resolves that under the trusted-proxy rules.
74
+ peer: (0, import_core.guarded)(() => (0, import_h3.getRequestIP)(event) ?? null, null),
75
+ env: globalThis.process?.env
76
+ };
77
+ const r = await cam.before(req, ctx);
78
+ if (!r) return;
79
+ if (r.response) return r.response;
80
+ const vars = r.vars;
81
+ event.context[VAR] = vars;
82
+ const res = event.node?.res;
83
+ if (vars.sessionCookie && vars.sid && res) {
84
+ const sid = vars.sid;
85
+ (0, import_core.guarded)(() => (0, import_h3.setCookie)(event, import_fetch.SESSION_COOKIE, sid, {
86
+ path: "/",
87
+ maxAge: import_fetch.SESSION_MAX_AGE,
88
+ httpOnly: true,
89
+ sameSite: "lax",
90
+ secure: new URL(req.url).protocol === "https:"
91
+ }), void 0);
92
+ }
93
+ if (typeof res?.once === "function") res.once("finish", () => cam.after(req, vars, res.statusCode));
94
+ else cam.after(req, vars, null);
95
+ });
96
+ }
97
+ var track = (event, et, data) => (0, import_fetch.track)(slotOf(event), et, data);
98
+ var scriptTag = (event) => (0, import_fetch.scriptTag)(slotOf(event));
99
+ function resetCamada() {
100
+ for (const c of instances) c.reset();
101
+ }
102
+
103
+ // src/nitro.ts
104
+ function camadaNitroPlugin() {
105
+ return (nitroApp) => {
106
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
107
+ const tag = scriptTag(event);
108
+ if (tag) html.head.push(tag);
109
+ });
110
+ };
111
+ }
112
+ // Annotate the CommonJS export names for ESM import in node:
113
+ 0 && (module.exports = {
114
+ camada,
115
+ camadaNitroPlugin,
116
+ resetCamada,
117
+ scriptTag,
118
+ track
119
+ });
120
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/camada.ts","../package.json","../src/version.ts","../src/nitro.ts"],"sourcesContent":["// @camada/nuxt — the two-file install for a Nuxt app (any Nitro preset with an h3 event):\n// server/middleware/camada.ts: export default camada(); // env: CAMADA_KEY (+ CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL in dev)\n// server/plugins/camada.ts: export default camadaNitroPlugin(); // the first-party beacon in every rendered <head>\n// track(event, 'login_failed', { user }) // an outcome the wire cannot show, from a server route\nexport { camada, track, scriptTag, resetCamada, type CamadaNuxtOptions, type CamadaNuxtVars } from './camada.js';\nexport { camadaNitroPlugin, type NitroAppLike } from './nitro.js';\n","// @camada/nuxt — the h3 binding over @camada/core/fetch. The pipeline (verdict, block,\n// challenge, beacon endpoints, the wire event) lives in core; this file only says what h3 knows:\n// the Web Request, the socket peer, the process env, and when the response has settled.\nimport { defineEventHandler, getRequestIP, setCookie, toWebRequest, type EventHandler, type H3Event } from 'h3';\nimport { guarded, TAP_NUXT } from '@camada/core';\nimport { createFetchCamada, SESSION_COOKIE, SESSION_MAX_AGE, track as coreTrack, scriptTag as coreScriptTag, type FetchCamada, type FetchCamadaOptions, type FetchRequestContext, type FetchVars } from '@camada/core/fetch';\nimport iife from '@camada/browser/iife-string';\nimport { SDK_ID } from './version.js';\n\nexport type CamadaNuxtOptions = FetchCamadaOptions;\nexport type CamadaNuxtVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.context\n\n// Every instance this module built, so a test or a hot reload can stop them all at once.\nconst instances = new Set<FetchCamada>();\n\n/** Reads the slot without trusting the event shape: a stub or a foreign event must not throw.\n * An inner event Nitro made for `event.$fetch` / SSR `useFetch` carries the outer event's context\n * on its mock request (`req.__unenv__`; Nitro copies only `_platform` and `waitUntil` into the\n * inner context), so the outer request's vars are found there — the page's own rid and session. */\nconst slotOf = (event: H3Event): FetchVars | undefined =>\n guarded(() => {\n const own = event.context[VAR] as FetchVars | undefined;\n if (own) return own;\n const outer = (event.node?.req as { __unenv__?: Record<string, unknown> } | undefined)?.__unenv__;\n return outer?.[VAR] as FetchVars | undefined;\n }, undefined);\n\n/**\n * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads\n * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key\n * it is inert. Returns a web Response where camada answers (block, challenge, the beacon\n * endpoints) and falls through otherwise.\n */\nexport function camada(opts: CamadaNuxtOptions = {}): EventHandler {\n const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the middleware may run on an edge preset\n instances.add(cam);\n return defineEventHandler(async (event) => {\n // Nitro's own `event.$fetch` / SSR `useFetch` re-enters the h3 app for the same page view,\n // already captured on the outer request (a bare `$fetch` with no event is a request of its own).\n if (slotOf(event)) return;\n const req = guarded(() => toWebRequest(event), null);\n if (!req) return; // an event h3 cannot express as a Request is not ours to break\n // Building the Request puts a Node request body into a web stream; hand that stream back to\n // h3 so the app's readBody() reads the same bytes rather than an already-drained socket.\n if (req.body) event._requestBody = req.body;\n const ctx: FetchRequestContext = {\n // The socket address (or what the host preset stamped as clientAddress); never\n // X-Forwarded-For here, core resolves that under the trusted-proxy rules.\n peer: guarded(() => getRequestIP(event) ?? null, null),\n env: globalThis.process?.env,\n };\n const r = await cam.before(req, ctx);\n if (!r) return;\n if (r.response) return r.response;\n const vars = r.vars;\n event.context[VAR] = vars;\n const res = event.node?.res;\n // Set before the app runs so a handler's own redirect carries the session too; core only\n // hands a cookie for a new session, so an existing `_sfp` is never overwritten. h3 1.x\n // writes cookies through node.res, so an event without one gets no session, quietly.\n if (vars.sessionCookie && vars.sid && res) {\n const sid = vars.sid;\n guarded(() => setCookie(event, SESSION_COOKIE, sid, {\n path: '/', maxAge: SESSION_MAX_AGE, httpOnly: true, sameSite: 'lax', secure: new URL(req.url).protocol === 'https:',\n }), undefined);\n }\n // Ship with the real status once the response has settled — a Node response, or the mock a\n // web preset provides. An event with no response object at all ships now, with st null.\n if (typeof res?.once === 'function') res.once('finish', () => cam.after(req, vars, res.statusCode));\n else cam.after(req, vars, null);\n });\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,\n * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`\n * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event\n * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and\n * a silent no-op where the middleware did not run.\n */\nexport const track = (event: H3Event, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: H3Event): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */\nexport function resetCamada(): void {\n for (const c of instances) c.reset();\n}\n","{\n \"name\": \"@camada/nuxt\",\n \"version\": \"0.1.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"description\": \"camada SDK for Nuxt (h3 server middleware): snapshot enforcement, first-party challenge and beacon, track() outcomes, events shipped with the settled status\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\"\n },\n \"peerDependencies\": {\n \"h3\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.10.0\",\n \"h3\": \"^1.15.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The Nitro side of the beacon: Nuxt renders `<head>` through the `render:html` hook, so the\n// tag goes in there rather than in every page. Typed structurally so this package never\n// depends on nitropack — the plugin file is `export default camadaNitroPlugin()`.\nimport type { H3Event } from 'h3';\nimport { scriptTag } from './camada.js';\n\nexport interface NitroAppLike { hooks: { hook(name: string, fn: (...a: any[]) => void): void } }\ninterface RenderHtml { head: string[] }\n\n/** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */\nexport function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void {\n return (nitroApp) => {\n nitroApp.hooks.hook('render:html', (html: RenderHtml, { event }: { event: H3Event }) => {\n const tag = scriptTag(event);\n if (tag) html.head.push(tag);\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGA,gBAA2G;AAC3G,kBAAkC;AAClC,mBAAwM;AACxM,yBAAiB;;;ACLf,WAAQ;AACR,cAAW;;;ACCN,IAAM,SAAS,GAAG,IAAI,IAAI,OAAO;;;AFSxC,IAAM,MAAM;AAGZ,IAAM,YAAY,oBAAI,IAAiB;AAMvC,IAAM,SAAS,CAAC,cACd,qBAAQ,MAAM;AACZ,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,MAAI,IAAK,QAAO;AAChB,QAAM,QAAS,MAAM,MAAM,KAA6D;AACxF,SAAO,QAAQ,GAAG;AACpB,GAAG,MAAS;AAQP,SAAS,OAAO,OAA0B,CAAC,GAAiB;AACjE,QAAM,UAAM,gCAAkB,EAAE,KAAK,sBAAU,KAAK,QAAQ,yBAAAA,QAAK,GAAG,IAAI;AACxE,YAAU,IAAI,GAAG;AACjB,aAAO,8BAAmB,OAAO,UAAU;AAGzC,QAAI,OAAO,KAAK,EAAG;AACnB,UAAM,UAAM,qBAAQ,UAAM,wBAAa,KAAK,GAAG,IAAI;AACnD,QAAI,CAAC,IAAK;AAGV,QAAI,IAAI,KAAM,OAAM,eAAe,IAAI;AACvC,UAAM,MAA2B;AAAA;AAAA;AAAA,MAG/B,UAAM,qBAAQ,UAAM,wBAAa,KAAK,KAAK,MAAM,IAAI;AAAA,MACrD,KAAK,WAAW,SAAS;AAAA,IAC3B;AACA,UAAM,IAAI,MAAM,IAAI,OAAO,KAAK,GAAG;AACnC,QAAI,CAAC,EAAG;AACR,QAAI,EAAE,SAAU,QAAO,EAAE;AACzB,UAAM,OAAO,EAAE;AACf,UAAM,QAAQ,GAAG,IAAI;AACrB,UAAM,MAAM,MAAM,MAAM;AAIxB,QAAI,KAAK,iBAAiB,KAAK,OAAO,KAAK;AACzC,YAAM,MAAM,KAAK;AACjB,+BAAQ,UAAM,qBAAU,OAAO,6BAAgB,KAAK;AAAA,QAClD,MAAM;AAAA,QAAK,QAAQ;AAAA,QAAiB,UAAU;AAAA,QAAM,UAAU;AAAA,QAAO,QAAQ,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa;AAAA,MAC7G,CAAC,GAAG,MAAS;AAAA,IACf;AAGA,QAAI,OAAO,KAAK,SAAS,WAAY,KAAI,KAAK,UAAU,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,UAAU,CAAC;AAAA,QAC7F,KAAI,MAAM,KAAK,MAAM,IAAI;AAAA,EAChC,CAAC;AACH;AASO,IAAM,QAAQ,CAAC,OAAgB,IAAY,aAA4C,aAAAC,OAAU,OAAO,KAAK,GAAG,IAAI,IAAI;AAGxH,IAAM,YAAY,CAAC,cAA2B,aAAAC,WAAc,OAAO,KAAK,CAAC;AAGzE,SAAS,cAAoB;AAClC,aAAW,KAAK,UAAW,GAAE,MAAM;AACrC;;;AGhFO,SAAS,oBAAsD;AACpE,SAAO,CAAC,aAAa;AACnB,aAAS,MAAM,KAAK,eAAe,CAAC,MAAkB,EAAE,MAAM,MAA0B;AACtF,YAAM,MAAM,UAAU,KAAK;AAC3B,UAAI,IAAK,MAAK,KAAK,KAAK,GAAG;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;","names":["iife","coreTrack","coreScriptTag"]}
@@ -0,0 +1,36 @@
1
+ import { EventHandler, H3Event } from 'h3';
2
+ import { FetchCamadaOptions, FetchVars } from '@camada/core/fetch';
3
+
4
+ type CamadaNuxtOptions = FetchCamadaOptions;
5
+ type CamadaNuxtVars = FetchVars;
6
+ /**
7
+ * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads
8
+ * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key
9
+ * it is inert. Returns a web Response where camada answers (block, challenge, the beacon
10
+ * endpoints) and falls through otherwise.
11
+ */
12
+ declare function camada(opts?: CamadaNuxtOptions): EventHandler;
13
+ /**
14
+ * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,
15
+ * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`
16
+ * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event
17
+ * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and
18
+ * a silent no-op where the middleware did not run.
19
+ */
20
+ declare const track: (event: H3Event, et: string, data?: {
21
+ user?: string;
22
+ }) => Promise<void>;
23
+ /** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */
24
+ declare const scriptTag: (event: H3Event) => string;
25
+ /** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */
26
+ declare function resetCamada(): void;
27
+
28
+ interface NitroAppLike {
29
+ hooks: {
30
+ hook(name: string, fn: (...a: any[]) => void): void;
31
+ };
32
+ }
33
+ /** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */
34
+ declare function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void;
35
+
36
+ export { type CamadaNuxtOptions, type CamadaNuxtVars, type NitroAppLike, camada, camadaNitroPlugin, resetCamada, scriptTag, track };
@@ -0,0 +1,36 @@
1
+ import { EventHandler, H3Event } from 'h3';
2
+ import { FetchCamadaOptions, FetchVars } from '@camada/core/fetch';
3
+
4
+ type CamadaNuxtOptions = FetchCamadaOptions;
5
+ type CamadaNuxtVars = FetchVars;
6
+ /**
7
+ * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads
8
+ * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key
9
+ * it is inert. Returns a web Response where camada answers (block, challenge, the beacon
10
+ * endpoints) and falls through otherwise.
11
+ */
12
+ declare function camada(opts?: CamadaNuxtOptions): EventHandler;
13
+ /**
14
+ * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,
15
+ * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`
16
+ * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event
17
+ * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and
18
+ * a silent no-op where the middleware did not run.
19
+ */
20
+ declare const track: (event: H3Event, et: string, data?: {
21
+ user?: string;
22
+ }) => Promise<void>;
23
+ /** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */
24
+ declare const scriptTag: (event: H3Event) => string;
25
+ /** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */
26
+ declare function resetCamada(): void;
27
+
28
+ interface NitroAppLike {
29
+ hooks: {
30
+ hook(name: string, fn: (...a: any[]) => void): void;
31
+ };
32
+ }
33
+ /** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */
34
+ declare function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void;
35
+
36
+ export { type CamadaNuxtOptions, type CamadaNuxtVars, type NitroAppLike, camada, camadaNitroPlugin, resetCamada, scriptTag, track };
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ // src/camada.ts
2
+ import { defineEventHandler, getRequestIP, setCookie, toWebRequest } from "h3";
3
+ import { guarded, TAP_NUXT } from "@camada/core";
4
+ import { createFetchCamada, SESSION_COOKIE, SESSION_MAX_AGE, track as coreTrack, scriptTag as coreScriptTag } from "@camada/core/fetch";
5
+ import iife from "@camada/browser/iife-string";
6
+
7
+ // package.json
8
+ var name = "@camada/nuxt";
9
+ var version = "0.1.0";
10
+
11
+ // src/version.ts
12
+ var SDK_ID = `${name}/${version}`;
13
+
14
+ // src/camada.ts
15
+ var VAR = "__camada";
16
+ var instances = /* @__PURE__ */ new Set();
17
+ var slotOf = (event) => guarded(() => {
18
+ const own = event.context[VAR];
19
+ if (own) return own;
20
+ const outer = event.node?.req?.__unenv__;
21
+ return outer?.[VAR];
22
+ }, void 0);
23
+ function camada(opts = {}) {
24
+ const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts);
25
+ instances.add(cam);
26
+ return defineEventHandler(async (event) => {
27
+ if (slotOf(event)) return;
28
+ const req = guarded(() => toWebRequest(event), null);
29
+ if (!req) return;
30
+ if (req.body) event._requestBody = req.body;
31
+ const ctx = {
32
+ // The socket address (or what the host preset stamped as clientAddress); never
33
+ // X-Forwarded-For here, core resolves that under the trusted-proxy rules.
34
+ peer: guarded(() => getRequestIP(event) ?? null, null),
35
+ env: globalThis.process?.env
36
+ };
37
+ const r = await cam.before(req, ctx);
38
+ if (!r) return;
39
+ if (r.response) return r.response;
40
+ const vars = r.vars;
41
+ event.context[VAR] = vars;
42
+ const res = event.node?.res;
43
+ if (vars.sessionCookie && vars.sid && res) {
44
+ const sid = vars.sid;
45
+ guarded(() => setCookie(event, SESSION_COOKIE, sid, {
46
+ path: "/",
47
+ maxAge: SESSION_MAX_AGE,
48
+ httpOnly: true,
49
+ sameSite: "lax",
50
+ secure: new URL(req.url).protocol === "https:"
51
+ }), void 0);
52
+ }
53
+ if (typeof res?.once === "function") res.once("finish", () => cam.after(req, vars, res.statusCode));
54
+ else cam.after(req, vars, null);
55
+ });
56
+ }
57
+ var track = (event, et, data) => coreTrack(slotOf(event), et, data);
58
+ var scriptTag = (event) => coreScriptTag(slotOf(event));
59
+ function resetCamada() {
60
+ for (const c of instances) c.reset();
61
+ }
62
+
63
+ // src/nitro.ts
64
+ function camadaNitroPlugin() {
65
+ return (nitroApp) => {
66
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
67
+ const tag = scriptTag(event);
68
+ if (tag) html.head.push(tag);
69
+ });
70
+ };
71
+ }
72
+ export {
73
+ camada,
74
+ camadaNitroPlugin,
75
+ resetCamada,
76
+ scriptTag,
77
+ track
78
+ };
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/camada.ts","../package.json","../src/version.ts","../src/nitro.ts"],"sourcesContent":["// @camada/nuxt — the h3 binding over @camada/core/fetch. The pipeline (verdict, block,\n// challenge, beacon endpoints, the wire event) lives in core; this file only says what h3 knows:\n// the Web Request, the socket peer, the process env, and when the response has settled.\nimport { defineEventHandler, getRequestIP, setCookie, toWebRequest, type EventHandler, type H3Event } from 'h3';\nimport { guarded, TAP_NUXT } from '@camada/core';\nimport { createFetchCamada, SESSION_COOKIE, SESSION_MAX_AGE, track as coreTrack, scriptTag as coreScriptTag, type FetchCamada, type FetchCamadaOptions, type FetchRequestContext, type FetchVars } from '@camada/core/fetch';\nimport iife from '@camada/browser/iife-string';\nimport { SDK_ID } from './version.js';\n\nexport type CamadaNuxtOptions = FetchCamadaOptions;\nexport type CamadaNuxtVars = FetchVars;\n\nconst VAR = '__camada'; // private: track() and scriptTag() are the API, not event.context\n\n// Every instance this module built, so a test or a hot reload can stop them all at once.\nconst instances = new Set<FetchCamada>();\n\n/** Reads the slot without trusting the event shape: a stub or a foreign event must not throw.\n * An inner event Nitro made for `event.$fetch` / SSR `useFetch` carries the outer event's context\n * on its mock request (`req.__unenv__`; Nitro copies only `_platform` and `waitUntil` into the\n * inner context), so the outer request's vars are found there — the page's own rid and session. */\nconst slotOf = (event: H3Event): FetchVars | undefined =>\n guarded(() => {\n const own = event.context[VAR] as FetchVars | undefined;\n if (own) return own;\n const outer = (event.node?.req as { __unenv__?: Record<string, unknown> } | undefined)?.__unenv__;\n return outer?.[VAR] as FetchVars | undefined;\n }, undefined);\n\n/**\n * The server middleware: `export default camada()` in `server/middleware/camada.ts`. Reads\n * `CAMADA_KEY` / `CAMADA_INGEST_URL` / `CAMADA_SNAPSHOT_URL` from `process.env`; without a key\n * it is inert. Returns a web Response where camada answers (block, challenge, the beacon\n * endpoints) and falls through otherwise.\n */\nexport function camada(opts: CamadaNuxtOptions = {}): EventHandler {\n const cam = createFetchCamada({ tap: TAP_NUXT, sdk: SDK_ID, iife }, opts); // mode defaults to lazy in core: the middleware may run on an edge preset\n instances.add(cam);\n return defineEventHandler(async (event) => {\n // Nitro's own `event.$fetch` / SSR `useFetch` re-enters the h3 app for the same page view,\n // already captured on the outer request (a bare `$fetch` with no event is a request of its own).\n if (slotOf(event)) return;\n const req = guarded(() => toWebRequest(event), null);\n if (!req) return; // an event h3 cannot express as a Request is not ours to break\n // Building the Request puts a Node request body into a web stream; hand that stream back to\n // h3 so the app's readBody() reads the same bytes rather than an already-drained socket.\n if (req.body) event._requestBody = req.body;\n const ctx: FetchRequestContext = {\n // The socket address (or what the host preset stamped as clientAddress); never\n // X-Forwarded-For here, core resolves that under the trusted-proxy rules.\n peer: guarded(() => getRequestIP(event) ?? null, null),\n env: globalThis.process?.env,\n };\n const r = await cam.before(req, ctx);\n if (!r) return;\n if (r.response) return r.response;\n const vars = r.vars;\n event.context[VAR] = vars;\n const res = event.node?.res;\n // Set before the app runs so a handler's own redirect carries the session too; core only\n // hands a cookie for a new session, so an existing `_sfp` is never overwritten. h3 1.x\n // writes cookies through node.res, so an event without one gets no session, quietly.\n if (vars.sessionCookie && vars.sid && res) {\n const sid = vars.sid;\n guarded(() => setCookie(event, SESSION_COOKIE, sid, {\n path: '/', maxAge: SESSION_MAX_AGE, httpOnly: true, sameSite: 'lax', secure: new URL(req.url).protocol === 'https:',\n }), undefined);\n }\n // Ship with the real status once the response has settled — a Node response, or the mock a\n // web preset provides. An event with no response object at all ships now, with st null.\n if (typeof res?.once === 'function') res.once('finish', () => cam.after(req, vars, res.statusCode));\n else cam.after(req, vars, null);\n });\n}\n\n/**\n * Records an outcome the app knows and the wire cannot show: `login_failed`, `login_succeeded`,\n * `signup`, `password_reset`, `mfa_failed`, `payment_failed`, `payment_succeeded`, `coupon_failed`\n * (free-form; that vocabulary is what the analyst's rules read). Joined to this request's event\n * through its rid and session; the user identifier is HMAC-hashed in-process. Never throws, and\n * a silent no-op where the middleware did not run.\n */\nexport const track = (event: H3Event, et: string, data?: { user?: string }): Promise<void> => coreTrack(slotOf(event), et, data);\n\n/** The `<script>` tag for an HTML response — `''` where the middleware did not run or the tenant turned the beacon off. */\nexport const scriptTag = (event: H3Event): string => coreScriptTag(slotOf(event));\n\n/** Test/reset hook: stops and drops every engine behind every `camada()` this module created. */\nexport function resetCamada(): void {\n for (const c of instances) c.reset();\n}\n","{\n \"name\": \"@camada/nuxt\",\n \"version\": \"0.1.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"description\": \"camada SDK for Nuxt (h3 server middleware): snapshot enforcement, first-party challenge and beacon, track() outcomes, events shipped with the settled status\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\"\n },\n \"peerDependencies\": {\n \"h3\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.10.0\",\n \"h3\": \"^1.15.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The Nitro side of the beacon: Nuxt renders `<head>` through the `render:html` hook, so the\n// tag goes in there rather than in every page. Typed structurally so this package never\n// depends on nitropack — the plugin file is `export default camadaNitroPlugin()`.\nimport type { H3Event } from 'h3';\nimport { scriptTag } from './camada.js';\n\nexport interface NitroAppLike { hooks: { hook(name: string, fn: (...a: any[]) => void): void } }\ninterface RenderHtml { head: string[] }\n\n/** `server/plugins/camada.ts`: pushes this request's beacon tag into the rendered `<head>`; nothing where the middleware did not run. */\nexport function camadaNitroPlugin(): (nitroApp: NitroAppLike) => void {\n return (nitroApp) => {\n nitroApp.hooks.hook('render:html', (html: RenderHtml, { event }: { event: H3Event }) => {\n const tag = scriptTag(event);\n if (tag) html.head.push(tag);\n });\n };\n}\n"],"mappings":";AAGA,SAAS,oBAAoB,cAAc,WAAW,oBAAqD;AAC3G,SAAS,SAAS,gBAAgB;AAClC,SAAS,mBAAmB,gBAAgB,iBAAiB,SAAS,WAAW,aAAa,qBAA0G;AACxM,OAAO,UAAU;;;ACLf,WAAQ;AACR,cAAW;;;ACCN,IAAM,SAAS,GAAG,IAAI,IAAI,OAAO;;;AFSxC,IAAM,MAAM;AAGZ,IAAM,YAAY,oBAAI,IAAiB;AAMvC,IAAM,SAAS,CAAC,UACd,QAAQ,MAAM;AACZ,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,MAAI,IAAK,QAAO;AAChB,QAAM,QAAS,MAAM,MAAM,KAA6D;AACxF,SAAO,QAAQ,GAAG;AACpB,GAAG,MAAS;AAQP,SAAS,OAAO,OAA0B,CAAC,GAAiB;AACjE,QAAM,MAAM,kBAAkB,EAAE,KAAK,UAAU,KAAK,QAAQ,KAAK,GAAG,IAAI;AACxE,YAAU,IAAI,GAAG;AACjB,SAAO,mBAAmB,OAAO,UAAU;AAGzC,QAAI,OAAO,KAAK,EAAG;AACnB,UAAM,MAAM,QAAQ,MAAM,aAAa,KAAK,GAAG,IAAI;AACnD,QAAI,CAAC,IAAK;AAGV,QAAI,IAAI,KAAM,OAAM,eAAe,IAAI;AACvC,UAAM,MAA2B;AAAA;AAAA;AAAA,MAG/B,MAAM,QAAQ,MAAM,aAAa,KAAK,KAAK,MAAM,IAAI;AAAA,MACrD,KAAK,WAAW,SAAS;AAAA,IAC3B;AACA,UAAM,IAAI,MAAM,IAAI,OAAO,KAAK,GAAG;AACnC,QAAI,CAAC,EAAG;AACR,QAAI,EAAE,SAAU,QAAO,EAAE;AACzB,UAAM,OAAO,EAAE;AACf,UAAM,QAAQ,GAAG,IAAI;AACrB,UAAM,MAAM,MAAM,MAAM;AAIxB,QAAI,KAAK,iBAAiB,KAAK,OAAO,KAAK;AACzC,YAAM,MAAM,KAAK;AACjB,cAAQ,MAAM,UAAU,OAAO,gBAAgB,KAAK;AAAA,QAClD,MAAM;AAAA,QAAK,QAAQ;AAAA,QAAiB,UAAU;AAAA,QAAM,UAAU;AAAA,QAAO,QAAQ,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa;AAAA,MAC7G,CAAC,GAAG,MAAS;AAAA,IACf;AAGA,QAAI,OAAO,KAAK,SAAS,WAAY,KAAI,KAAK,UAAU,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,UAAU,CAAC;AAAA,QAC7F,KAAI,MAAM,KAAK,MAAM,IAAI;AAAA,EAChC,CAAC;AACH;AASO,IAAM,QAAQ,CAAC,OAAgB,IAAY,SAA4C,UAAU,OAAO,KAAK,GAAG,IAAI,IAAI;AAGxH,IAAM,YAAY,CAAC,UAA2B,cAAc,OAAO,KAAK,CAAC;AAGzE,SAAS,cAAoB;AAClC,aAAW,KAAK,UAAW,GAAE,MAAM;AACrC;;;AGhFO,SAAS,oBAAsD;AACpE,SAAO,CAAC,aAAa;AACnB,aAAS,MAAM,KAAK,eAAe,CAAC,MAAkB,EAAE,MAAM,MAA0B;AACtF,YAAM,MAAM,UAAU,KAAK;AAC3B,UAAI,IAAK,MAAK,KAAK,KAAK,GAAG;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@camada/nuxt",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "description": "camada SDK for Nuxt (h3 server middleware): snapshot enforcement, first-party challenge and beacon, track() outcomes, events shipped with the settled status",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "main": "./dist/index.cjs",
23
+ "module": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "dependencies": {
26
+ "@camada/core": "^0.3.0",
27
+ "@camada/browser": "^0.2.0"
28
+ },
29
+ "peerDependencies": {
30
+ "h3": "^1.12.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/camada-app/camada-nuxt.git"
35
+ },
36
+ "homepage": "https://github.com/camada-app/camada-nuxt#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/camada-app/camada-nuxt/issues"
39
+ }
40
+ }