@polishd/next 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.
Files changed (127) hide show
  1. package/AGENTS.md +262 -0
  2. package/DATABASE.md +147 -0
  3. package/LICENSE +21 -0
  4. package/README.md +310 -0
  5. package/bin/init.mjs +224 -0
  6. package/dist/ai/actions.d.ts +23 -0
  7. package/dist/ai/actions.d.ts.map +1 -0
  8. package/dist/ai/actions.js +58 -0
  9. package/dist/ai/actions.js.map +1 -0
  10. package/dist/ai/digest.d.ts +9 -0
  11. package/dist/ai/digest.d.ts.map +1 -0
  12. package/dist/ai/digest.js +108 -0
  13. package/dist/ai/digest.js.map +1 -0
  14. package/dist/ai/github.d.ts +51 -0
  15. package/dist/ai/github.d.ts.map +1 -0
  16. package/dist/ai/github.js +175 -0
  17. package/dist/ai/github.js.map +1 -0
  18. package/dist/ai/guard.d.ts +43 -0
  19. package/dist/ai/guard.d.ts.map +1 -0
  20. package/dist/ai/guard.js +57 -0
  21. package/dist/ai/guard.js.map +1 -0
  22. package/dist/ai/issues.d.ts +21 -0
  23. package/dist/ai/issues.d.ts.map +1 -0
  24. package/dist/ai/issues.js +291 -0
  25. package/dist/ai/issues.js.map +1 -0
  26. package/dist/ai/profile.d.ts +26 -0
  27. package/dist/ai/profile.d.ts.map +1 -0
  28. package/dist/ai/profile.js +160 -0
  29. package/dist/ai/profile.js.map +1 -0
  30. package/dist/ai/providers.d.ts +40 -0
  31. package/dist/ai/providers.d.ts.map +1 -0
  32. package/dist/ai/providers.js +142 -0
  33. package/dist/ai/providers.js.map +1 -0
  34. package/dist/ai/scan.d.ts +38 -0
  35. package/dist/ai/scan.d.ts.map +1 -0
  36. package/dist/ai/scan.js +274 -0
  37. package/dist/ai/scan.js.map +1 -0
  38. package/dist/ai/settings.d.ts +31 -0
  39. package/dist/ai/settings.d.ts.map +1 -0
  40. package/dist/ai/settings.js +182 -0
  41. package/dist/ai/settings.js.map +1 -0
  42. package/dist/ai/summary.d.ts +26 -0
  43. package/dist/ai/summary.d.ts.map +1 -0
  44. package/dist/ai/summary.js +260 -0
  45. package/dist/ai/summary.js.map +1 -0
  46. package/dist/ai/types.d.ts +188 -0
  47. package/dist/ai/types.d.ts.map +1 -0
  48. package/dist/ai/types.js +13 -0
  49. package/dist/ai/types.js.map +1 -0
  50. package/dist/client/init.d.ts +14 -0
  51. package/dist/client/init.d.ts.map +1 -0
  52. package/dist/client/init.js +274 -0
  53. package/dist/client/init.js.map +1 -0
  54. package/dist/client/monitor.d.ts +23 -0
  55. package/dist/client/monitor.d.ts.map +1 -0
  56. package/dist/client/monitor.js +176 -0
  57. package/dist/client/monitor.js.map +1 -0
  58. package/dist/client.d.ts +11 -0
  59. package/dist/client.d.ts.map +1 -0
  60. package/dist/client.js +10 -0
  61. package/dist/client.js.map +1 -0
  62. package/dist/config.d.ts +36 -0
  63. package/dist/config.d.ts.map +1 -0
  64. package/dist/config.js +26 -0
  65. package/dist/config.js.map +1 -0
  66. package/dist/dashboard/elements.d.ts +6 -0
  67. package/dist/dashboard/elements.d.ts.map +1 -0
  68. package/dist/dashboard/elements.js +35 -0
  69. package/dist/dashboard/elements.js.map +1 -0
  70. package/dist/dashboard/features.d.ts +6 -0
  71. package/dist/dashboard/features.d.ts.map +1 -0
  72. package/dist/dashboard/features.js +28 -0
  73. package/dist/dashboard/features.js.map +1 -0
  74. package/dist/dashboard/index.d.ts +54 -0
  75. package/dist/dashboard/index.d.ts.map +1 -0
  76. package/dist/dashboard/index.js +140 -0
  77. package/dist/dashboard/index.js.map +1 -0
  78. package/dist/dashboard/journeys.d.ts +5 -0
  79. package/dist/dashboard/journeys.d.ts.map +1 -0
  80. package/dist/dashboard/journeys.js +158 -0
  81. package/dist/dashboard/journeys.js.map +1 -0
  82. package/dist/dashboard/pages.d.ts +5 -0
  83. package/dist/dashboard/pages.d.ts.map +1 -0
  84. package/dist/dashboard/pages.js +104 -0
  85. package/dist/dashboard/pages.js.map +1 -0
  86. package/dist/dashboard/summary.d.ts +13 -0
  87. package/dist/dashboard/summary.d.ts.map +1 -0
  88. package/dist/dashboard/summary.js +333 -0
  89. package/dist/dashboard/summary.js.map +1 -0
  90. package/dist/index.d.ts +10 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +10 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/proxy.d.ts +40 -0
  95. package/dist/proxy.d.ts.map +1 -0
  96. package/dist/proxy.js +29 -0
  97. package/dist/proxy.js.map +1 -0
  98. package/dist/route.d.ts +24 -0
  99. package/dist/route.d.ts.map +1 -0
  100. package/dist/route.js +37 -0
  101. package/dist/route.js.map +1 -0
  102. package/dist/server/ingest.d.ts +14 -0
  103. package/dist/server/ingest.d.ts.map +1 -0
  104. package/dist/server/ingest.js +73 -0
  105. package/dist/server/ingest.js.map +1 -0
  106. package/dist/server/queries.d.ts +245 -0
  107. package/dist/server/queries.d.ts.map +1 -0
  108. package/dist/server/queries.js +547 -0
  109. package/dist/server/queries.js.map +1 -0
  110. package/dist/server/store.d.ts +29 -0
  111. package/dist/server/store.d.ts.map +1 -0
  112. package/dist/server/store.js +359 -0
  113. package/dist/server/store.js.map +1 -0
  114. package/dist/server.d.ts +15 -0
  115. package/dist/server.d.ts.map +1 -0
  116. package/dist/server.js +15 -0
  117. package/dist/server.js.map +1 -0
  118. package/dist/session.d.ts +16 -0
  119. package/dist/session.d.ts.map +1 -0
  120. package/dist/session.js +29 -0
  121. package/dist/session.js.map +1 -0
  122. package/dist/shared/types.d.ts +56 -0
  123. package/dist/shared/types.d.ts.map +1 -0
  124. package/dist/shared/types.js +27 -0
  125. package/dist/shared/types.js.map +1 -0
  126. package/docs/SETUP.md +315 -0
  127. package/package.json +92 -0
package/AGENTS.md ADDED
@@ -0,0 +1,262 @@
1
+ # AGENTS.md — installing `@polishd/next` into an app
2
+
3
+ Instructions for a coding agent asked to add Polishd to a Next.js application.
4
+ Human-oriented prose lives in [README.md](./README.md) and
5
+ [docs/SETUP.md](./docs/SETUP.md); this file is the procedure.
6
+
7
+ Work through the phases in order. Do not skip verification — several of the
8
+ constraints below fail only at build time or, worse, silently at runtime.
9
+
10
+ ---
11
+
12
+ ## Phase 1 — Check preconditions
13
+
14
+ Read these from the target app before changing anything.
15
+
16
+ | Check | How | If it fails |
17
+ |---|---|---|
18
+ | Next.js App Router | an `app/` or `src/app/` directory exists | Stop. Polishd has no Pages Router support. |
19
+ | Next.js ≥ 15 | `next` version in `package.json` | Stop and report. |
20
+ | Node ≥ 22.5 | `node -v`, plus `engines`/`.nvmrc`/host setting | Capture silently no-ops on older Node. Warn the user and continue only if they accept that. |
21
+ | Tailwind CSS | `tailwindcss` in dependencies | The dashboard renders unstyled. See Phase 4. |
22
+ | Package manager | lockfile name | Use it consistently; don't mix. |
23
+
24
+ Record two facts you will need throughout:
25
+
26
+ - **`BASE`** — `src` if `src/app/` exists, otherwise `` (repo root).
27
+ - **`NEXT_MAJOR`** — 16+ uses `proxy.ts`; 15 uses `middleware.ts`.
28
+
29
+ ## Phase 2 — Install
30
+
31
+ ```bash
32
+ npm install @polishd/next
33
+ ```
34
+
35
+ `pg` arrives automatically as an optional dependency; it is only loaded when a
36
+ Postgres URL is configured. No separate install is needed.
37
+
38
+ ## Phase 3 — Scaffold
39
+
40
+ Prefer the CLI. It handles every branch in Phase 1 and Phase 4 for you:
41
+
42
+ ```bash
43
+ npx polishd init
44
+ ```
45
+
46
+ Flags: `--dry-run` (preview), `--force` (overwrite existing), `--js` (JavaScript
47
+ output), `--config` (also emit `polishd.config.ts`).
48
+
49
+ It writes four files, appends `.polishd/` to `.gitignore`, and wires Tailwind.
50
+ If it reports that a file already exists, do not overwrite blindly — merge by
51
+ hand following Phase 3b.
52
+
53
+ ### Phase 3b — Manual scaffold (only if the CLI could not run)
54
+
55
+ Create exactly these four files. Contents are load-bearing; see Phase 5.
56
+
57
+ **1. `{BASE}/proxy.ts`** — mints the anonymous session cookie.
58
+ On Next 15 name the file `middleware.ts` and the function `middleware`.
59
+
60
+ ```ts
61
+ import type { NextRequest } from "next/server";
62
+ import { withPolishdSession } from "@polishd/next/proxy";
63
+
64
+ export function proxy(request: NextRequest) {
65
+ return withPolishdSession(request);
66
+ }
67
+
68
+ export const config = {
69
+ matcher: ["/((?!api|_next/static|_next/image|favicon|assets).*)"],
70
+ };
71
+ ```
72
+
73
+ If the app already has a proxy/middleware, do **not** replace it. Call
74
+ `withPolishdSession(request, existingResponse)` in the existing handler and
75
+ merge the matcher, keeping the `api` exclusion.
76
+
77
+ **2. `{BASE}/instrumentation-client.ts`** — starts browser capture.
78
+
79
+ ```ts
80
+ import { initPolishd } from "@polishd/next/client";
81
+
82
+ initPolishd();
83
+ ```
84
+
85
+ **3. `{BASE}/app/api/polishd/route.ts`** — the ingest endpoint.
86
+
87
+ ```ts
88
+ import { createPolishdRoute } from "@polishd/next/route";
89
+
90
+ export const runtime = "nodejs";
91
+ export const dynamic = "force-dynamic";
92
+
93
+ export const POST = createPolishdRoute();
94
+ ```
95
+
96
+ **4. `{BASE}/app/polishd/page.tsx`** — the dashboard.
97
+
98
+ ```tsx
99
+ import { createPolishdPage } from "@polishd/next/dashboard";
100
+
101
+ export const runtime = "nodejs";
102
+ export const dynamic = "force-dynamic";
103
+
104
+ export default createPolishdPage();
105
+ ```
106
+
107
+ Then add `.polishd/` to `.gitignore`.
108
+
109
+ ## Phase 4 — Wire Tailwind
110
+
111
+ The dashboard is built from Tailwind utility classes and ships **no stylesheet**.
112
+ Tailwind will not find it on its own — v4's content detection deliberately
113
+ ignores `node_modules`, and v3 only scans the globs you give it. Skipping this
114
+ step produces a dashboard that returns HTTP 200 and is completely unstyled.
115
+
116
+ **Tailwind v4** (no `tailwind.config.*`; CSS has `@import "tailwindcss"`) — add
117
+ to that CSS file, with the path relative to the file itself:
118
+
119
+ ```css
120
+ @import "tailwindcss";
121
+ @source "../../node_modules/@polishd/next/dist";
122
+ ```
123
+
124
+ **Tailwind v3** — add to `content` in `tailwind.config.*`:
125
+
126
+ ```ts
127
+ content: ["./src/**/*.{ts,tsx}", "./node_modules/@polishd/next/dist/**/*.js"],
128
+ ```
129
+
130
+ **No Tailwind** — tell the user the dashboard will be unstyled. Do not add
131
+ Tailwind to their app to fix this unless they ask.
132
+
133
+ ## Phase 5 — Invariants
134
+
135
+ Violating any of these produces a broken build or a security hole. They are not
136
+ style preferences.
137
+
138
+ 1. **`config.matcher` must be an inline array literal.** Next parses it
139
+ statically; it cannot be imported, spread, or re-exported. It must exclude
140
+ all of `/api` — under Next 16 + Turbopack a matcher that touches any
141
+ `/api/*` route breaks resolution for the entire `/api` segment.
142
+
143
+ 2. **`runtime` and `dynamic` must be declared inline** in both the route and the
144
+ page module. `export { POST, runtime, dynamic } from "@polishd/next/route"`
145
+ fails the build with *"Next.js can't recognize the exported `runtime` field
146
+ in route. It mustn't be reexported."*
147
+
148
+ 3. **Gate the dashboard in production.** With no `authenticate` callback, the
149
+ dashboard *and its server actions* are public: anyone reaching the URL can
150
+ read the analytics, change the configured model and API base URL, and spend
151
+ the owner's model tokens. See Phase 6.
152
+
153
+ 4. **Never import `@polishd/next/server` from a client component.** It pulls
154
+ database drivers into the browser bundle. Use `@polishd/next/client` there.
155
+
156
+ 5. **`runtime` must be `"nodejs"`.** The store uses `node:sqlite`/`pg`; neither
157
+ runs on the Edge runtime.
158
+
159
+ ## Phase 6 — Protect the dashboard
160
+
161
+ If the app has any auth helper, wire it up. Ask the user which one rather than
162
+ guessing.
163
+
164
+ ```tsx
165
+ import { createPolishdPage } from "@polishd/next/dashboard";
166
+ import { isAuthenticated } from "@/lib/auth";
167
+ import { MyLogin } from "./login";
168
+
169
+ export const runtime = "nodejs";
170
+ export const dynamic = "force-dynamic";
171
+
172
+ export default createPolishdPage({
173
+ authenticate: isAuthenticated, // () => boolean | Promise<boolean>
174
+ unauthorized: <MyLogin />, // optional; a minimal screen otherwise
175
+ });
176
+ ```
177
+
178
+ The same callback guards the AI server actions, re-running per request against
179
+ the caller's cookies. Polishd ships no auth code of its own.
180
+
181
+ A common pattern is to stay open in development and require auth in production:
182
+
183
+ ```tsx
184
+ authenticate: async () =>
185
+ process.env.NODE_ENV !== "production" || (await isAuthenticated()),
186
+ ```
187
+
188
+ ## Phase 7 — Verify
189
+
190
+ Run all of these. Report the results; do not declare success from the build
191
+ alone.
192
+
193
+ ```bash
194
+ # 1. Build must pass.
195
+ npm run build
196
+
197
+ # 2. Serve it.
198
+ npm start &
199
+
200
+ # 3. The proxy mints a session cookie on a page request.
201
+ curl -sI http://localhost:3000/ | grep -i set-cookie
202
+ # expect: polishd_session=<uuid>; Path=/; HttpOnly; SameSite=lax
203
+
204
+ # 4. The dashboard responds.
205
+ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/polishd
206
+ # expect: 200
207
+
208
+ # 5. Ingest accepts a batch (reuse the cookie from step 3).
209
+ curl -s -X POST http://localhost:3000/api/polishd \
210
+ -H 'content-type: application/json' \
211
+ -H 'cookie: polishd_session=<uuid>' \
212
+ -d '{"events":[{"type":"page_view","ts":1,"path":"/"}]}'
213
+ # expect: {"ok":true,"stored":1}
214
+
215
+ # 6. Styling is present — the stylesheet must contain dashboard utilities.
216
+ # Fetch the dashboard's CSS chunk and grep for `tabular-nums`.
217
+ ```
218
+
219
+ In development the store writes `.polishd/analytics.db` (SQLite). Its presence
220
+ after step 5 confirms the write path end to end.
221
+
222
+ ## Phase 8 — Production
223
+
224
+ Capture only persists in production once a Postgres URL is set — serverless
225
+ hosts have read-only, ephemeral filesystems, so the dev SQLite file cannot
226
+ survive there.
227
+
228
+ | Variable | Required | Purpose |
229
+ |---|---|---|
230
+ | `POLISHD_DATABASE_URL` | for production capture | Pooled Postgres connection string |
231
+ | `POLISHD_DB_PATH` | no | Custom SQLite path in dev |
232
+ | `POLISHD_AI_*` | no | Preset AI provider/model/key instead of using the dashboard |
233
+ | `POLISHD_GITHUB_*` | no | Repo + token for filing issues from findings |
234
+
235
+ Set the host's Node version to 22.x or later. See [DATABASE.md](./DATABASE.md)
236
+ for provisioning and schema.
237
+
238
+ Without a database URL the package degrades to a safe no-op: a console warning,
239
+ a notice on the dashboard, and no impact on the host app.
240
+
241
+ ## Troubleshooting
242
+
243
+ | Symptom | Cause | Fix |
244
+ |---|---|---|
245
+ | *"can't recognize the exported `runtime` field"* | Route segment config re-exported | Declare `runtime`/`dynamic` inline (Phase 3b) |
246
+ | Every `/api/*` route 404s | Proxy matcher matches `/api` | Restore the `api` exclusion in the matcher |
247
+ | Dashboard renders unstyled | Tailwind isn't scanning `dist` | Phase 4 |
248
+ | Dashboard is empty, warning about the store | No writable backend | Set `POLISHD_DATABASE_URL`, or check Node ≥ 22.5 in dev |
249
+ | `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite` | Node < 22.5 | Upgrade Node, or configure Postgres |
250
+ | No `polishd_session` cookie | Proxy file missing/misnamed, or matcher excludes the path | Check `proxy.ts` (Next 16) vs `middleware.ts` (Next 15) |
251
+ | Database drivers in the client bundle | `@polishd/next/server` imported client-side | Import from `@polishd/next/client` |
252
+
253
+ ## Entry points
254
+
255
+ | Import | Contents | Environment |
256
+ |---|---|---|
257
+ | `@polishd/next` | config, `definePolishdConfig`, event types | isomorphic |
258
+ | `@polishd/next/client` | `initPolishd`, `PolishdMonitor` | browser |
259
+ | `@polishd/next/server` | store, ingest, queries, `withPolishdSession` | Node only |
260
+ | `@polishd/next/route` | `POST`, `createPolishdRoute` | Node only |
261
+ | `@polishd/next/proxy` | `proxy`, `config`, `withPolishdSession`, `polishdMatcher` | Edge-safe |
262
+ | `@polishd/next/dashboard` | `createPolishdPage`, `PolishdDashboard` | Node only |
package/DATABASE.md ADDED
@@ -0,0 +1,147 @@
1
+ # Polishd — Database setup
2
+
3
+ Polishd captures a high-frequency stream of behavioral events. In **local
4
+ development** it writes them to a SQLite file with zero configuration. In
5
+ **production** you must point it at a writable, network-accessible database,
6
+ because most modern hosts (Vercel, Netlify, Cloudflare) run your app on a
7
+ **read-only, ephemeral filesystem** — a local SQLite file there either fails to
8
+ open or is wiped on every cold start.
9
+
10
+ Crucially, Polishd events must **not** go through the same GitHub-commit storage
11
+ the notes content uses: a commit (and redeploy) per event would be catastrophic.
12
+ Analytics needs a real database.
13
+
14
+ ---
15
+
16
+ ## How the store decides what to use
17
+
18
+ `src/server/store.ts` opens its backend lazily and picks it in this
19
+ order:
20
+
21
+ 1. **`POLISHD_DATABASE_URL`** is set → use that Postgres database (production).
22
+ 2. Otherwise → use SQLite at **`POLISHD_DB_PATH`** (defaults to
23
+ `.polishd/analytics.db`), good for local dev.
24
+ 3. If neither can be opened (read-only FS, bad URL) → the store **latches to a
25
+ safe no-op**: capture silently drops, the `/polishd` dashboard shows a notice,
26
+ and the host app is never taken down.
27
+
28
+ > Both backends sit behind one async interface (`storeReady`, `insertEvents`,
29
+ > the `query` helper), so no caller knows which is live. Aggregation SQL is
30
+ > written in a portable subset — `?` placeholders (rewritten to `$1, $2, …` for
31
+ > Postgres) and `SUM(CASE WHEN … THEN 1 ELSE 0 END)` rather than SQLite's
32
+ > `SUM(x = y)` — so the same queries run on either backend.
33
+ >
34
+ > The Postgres backend uses the [`pg`](https://www.npmjs.com/package/pg) driver
35
+ > and requires the Node.js runtime (the ingest route and dashboard already pin
36
+ > `runtime = "nodejs"`). It connects over TLS with `rejectUnauthorized: false`,
37
+ > which every managed host (Neon, Supabase, RDS) needs.
38
+
39
+ ---
40
+
41
+ ## Local development (default — nothing to do)
42
+
43
+ ```bash
44
+ npm run dev
45
+ ```
46
+
47
+ Events land in `.polishd/analytics.db` (gitignored). Inspect them directly:
48
+
49
+ ```bash
50
+ node -e "const {DatabaseSync}=require('node:sqlite'); \
51
+ const db=new DatabaseSync('.polishd/analytics.db'); \
52
+ console.log(db.prepare('SELECT type, count(*) c FROM events GROUP BY type').all())"
53
+ ```
54
+
55
+ To use a custom path (e.g. a tmpfs-backed location), set `POLISHD_DB_PATH`.
56
+
57
+ ---
58
+
59
+ ## Production — Option A: Vercel Postgres / Neon (recommended)
60
+
61
+ Serverless Postgres with connection pooling, which matters because each
62
+ serverless invocation opens its own connection.
63
+
64
+ 1. Create a database: **Vercel Dashboard → Storage → Create → Postgres**
65
+ (Vercel provisions a [Neon](https://neon.tech) database), or sign up at
66
+ neon.tech directly.
67
+ 2. Copy the **pooled** connection string (host contains `-pooler`).
68
+ 3. Add it to your environment:
69
+
70
+ ```bash
71
+ # Vercel: Project → Settings → Environment Variables
72
+ POLISHD_DATABASE_URL=postgres://user:password@ep-xxx-pooler.region.aws.neon.tech/polishd?sslmode=require
73
+ ```
74
+
75
+ 4. Redeploy. The store creates the `events` table on first write.
76
+
77
+ ### Schema (created automatically; shown for reference)
78
+
79
+ ```sql
80
+ CREATE TABLE IF NOT EXISTS events (
81
+ id BIGSERIAL PRIMARY KEY,
82
+ session_id TEXT NOT NULL,
83
+ type TEXT NOT NULL,
84
+ ts BIGINT NOT NULL,
85
+ path TEXT NOT NULL,
86
+ selector TEXT,
87
+ component TEXT,
88
+ text TEXT,
89
+ value DOUBLE PRECISION,
90
+ meta JSONB,
91
+ received_at BIGINT NOT NULL
92
+ );
93
+ CREATE INDEX IF NOT EXISTS idx_events_path ON events(path);
94
+ CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
95
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Production — Option B: Turso (edge SQLite) — not yet wired up
101
+
102
+ > **Not implemented.** `POLISHD_DATABASE_URL` currently selects the **Postgres**
103
+ > backend only (via the `pg` driver). A `libsql://` URL will fail to connect and
104
+ > the store will latch to no-op. The libSQL adapter is a follow-up; until then,
105
+ > use Option A. The interface is backend-agnostic, so adding it is isolated to
106
+ > `openPostgres`'s sibling in `store.ts`.
107
+
108
+ If you prefer to keep SQLite semantics at the edge, the plan is to use
109
+ [Turso](https://turso.tech) (libSQL):
110
+
111
+ ```bash
112
+ turso db create polishd
113
+ turso db show polishd --url # → POLISHD_DATABASE_URL (libsql://...)
114
+ turso db tokens create polishd # → POLISHD_DATABASE_AUTH_TOKEN
115
+ ```
116
+
117
+ The same `events` schema applies (SQLite types, as in `store.ts`).
118
+
119
+ ---
120
+
121
+ ## Environment variable reference
122
+
123
+ | Variable | Required | Purpose |
124
+ | ---------------------------- | ----------------- | ------------------------------------------------------------- |
125
+ | `POLISHD_DATABASE_URL` | Production | Postgres or libSQL connection string. Presence selects it. |
126
+ | `POLISHD_DATABASE_AUTH_TOKEN` | Turso only | Auth token for the libSQL database. |
127
+ | `POLISHD_DB_PATH` | No | Custom local SQLite path (dev). Default `.polishd/analytics.db`. |
128
+
129
+ No secrets ever live in `polishd.config.ts` — that file is imported by the
130
+ browser. Connection strings and tokens are read from `process.env` on the
131
+ server only.
132
+
133
+ ---
134
+
135
+ ## Data retention & privacy
136
+
137
+ - Events carry **no PII**: sessions are random UUIDs (no fingerprinting), and
138
+ only element *labels* are stored — never user-typed input.
139
+ - Prune old rows on whatever cadence you like, e.g. a scheduled job:
140
+
141
+ ```sql
142
+ DELETE FROM events WHERE received_at < (extract(epoch from now()) - 60*60*24*90) * 1000;
143
+ ```
144
+
145
+ (90-day retention; `received_at` is milliseconds since epoch.)
146
+ - The capture layer honors the browser's **Do Not Track** signal and the
147
+ `sampleRate` config, so you can dial volume down without code changes.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muskan Vaswan
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.