@nerdsystem/sdk 0.0.7 → 0.0.9
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 +27 -0
- package/dist/auth/client.d.ts +3 -0
- package/dist/auth/client.js +14 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,3 +14,30 @@ export default app;
|
|
|
14
14
|
`createNerdApp()` mounts session routes at `/api/auth` (signup, login, logout, me, forgot, reset), sets security headers on every response, and populates `c.var.user` from the httpOnly session cookie. Tokens are verified offline against the platform's JWKS.
|
|
15
15
|
|
|
16
16
|
Start from the template with `npx nerdsystem init my-app`. Documentation lives in the template's `AGENTS.md` and `.claude/skills/nerdsystem/SKILL.md`.
|
|
17
|
+
|
|
18
|
+
## Adding the SDK to an existing Vite + Hono app
|
|
19
|
+
|
|
20
|
+
`nerd init` is the only scaffold and it refuses a folder that is not empty. A Vite client with a Hono server can be brought into the same shape by hand; `nerd check` refuses anything else; steps 1, 2, 4, 6, and 7 are its checks, and steps 3 and 5 are what the deploy relies on.
|
|
21
|
+
|
|
22
|
+
1. `npm install @nerdsystem/sdk`. The check reads `package.json` and requires it under `dependencies`.
|
|
23
|
+
2. The Worker entry is `src/server/index.ts`. In that file, replace the root `new Hono()` with `createNerdApp()` from `@nerdsystem/sdk` and end with `export default app`. Sub-routers in other files can stay `new Hono<NerdHonoEnv>()`.
|
|
24
|
+
3. Mount every router under `/api/...`. Any other path is served from the static client and does not reach the Worker.
|
|
25
|
+
4. `wrangler.jsonc` carries only three bindings, `DB`, `STORAGE`, and `ASSETS`, with `run_worker_first` set for `/api/*`. No other binding and no Durable Objects. The platform provisions `DB` and `STORAGE` at deploy time, so the local ids are placeholders:
|
|
26
|
+
|
|
27
|
+
```jsonc
|
|
28
|
+
{
|
|
29
|
+
"name": "my-app",
|
|
30
|
+
"main": "src/server/index.ts",
|
|
31
|
+
"compatibility_date": "2026-11-02",
|
|
32
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
33
|
+
"assets": { "binding": "ASSETS", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*"] },
|
|
34
|
+
"d1_databases": [{ "binding": "DB", "database_name": "my-app", "database_id": "local", "migrations_dir": "migrations" }],
|
|
35
|
+
"r2_buckets": [{ "binding": "STORAGE", "bucket_name": "my-app" }]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
5. Build with Vite and `@cloudflare/vite-plugin` (`plugins: [react(), cloudflare()]` in `vite.config.ts`). `nerd deploy` reads `dist/<worker>/wrangler.json`, which that plugin writes.
|
|
40
|
+
6. No `package.json` script may run `wrangler deploy`, `wrangler secret`, or `wrangler d1 migrations apply` against Cloudflare; `nerd` does those.
|
|
41
|
+
7. `nerd check` runs `tsc -b`, so keep a `tsconfig.json` that builds both the server and the client. A missing `.github/dependabot.yml` is reported as a warning, not a refusal.
|
|
42
|
+
|
|
43
|
+
Then `nerd link --slug <project>`, which writes `nerd.json` and creates the project if it does not exist yet, and `nerd check`. Sign-in moves to the SDK's `/api/auth` routes, so delete any login, signup, or password code of your own; the app's users sign up again on the new login.
|
package/dist/auth/client.d.ts
CHANGED
|
@@ -28,6 +28,9 @@ export declare class AuthServiceClient {
|
|
|
28
28
|
/** The visitor's address, forwarded so the platform rate-limits per person rather than per app. */
|
|
29
29
|
clientIp?: string | undefined);
|
|
30
30
|
get jwksUrl(): string;
|
|
31
|
+
private signature?;
|
|
32
|
+
/** HMAC of the visitor address keyed by SHA-256(tenant secret), which is what the platform stores; an unsigned address is ignored there. */
|
|
33
|
+
private signVisitor;
|
|
31
34
|
private call;
|
|
32
35
|
signup(input: {
|
|
33
36
|
email: string;
|
package/dist/auth/client.js
CHANGED
|
@@ -28,13 +28,26 @@ export class AuthServiceClient {
|
|
|
28
28
|
get jwksUrl() {
|
|
29
29
|
return `${this.env.NERD_AUTH_URL.replace(/\/$/, "")}/.well-known/jwks.json`;
|
|
30
30
|
}
|
|
31
|
+
signature;
|
|
32
|
+
/** HMAC of the visitor address keyed by SHA-256(tenant secret), which is what the platform stores; an unsigned address is ignored there. */
|
|
33
|
+
signVisitor(ip) {
|
|
34
|
+
this.signature ??= (async () => {
|
|
35
|
+
const enc = new TextEncoder();
|
|
36
|
+
const digest = await crypto.subtle.digest("SHA-256", enc.encode(this.env.NERD_AUTH_SECRET));
|
|
37
|
+
const keyHex = Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
38
|
+
const key = await crypto.subtle.importKey("raw", enc.encode(keyHex), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
39
|
+
return Array.from(new Uint8Array(await crypto.subtle.sign("HMAC", key, enc.encode(ip))), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
40
|
+
})();
|
|
41
|
+
return this.signature;
|
|
42
|
+
}
|
|
31
43
|
async call(method, path, body, auth) {
|
|
44
|
+
const visitor = this.clientIp && this.env.NERD_AUTH_SECRET ? { "x-nerd-client-ip": this.clientIp, "x-nerd-client-sig": await this.signVisitor(this.clientIp) } : {};
|
|
32
45
|
const res = await this.fetchImpl(`${this.base}${path}`, {
|
|
33
46
|
method,
|
|
34
47
|
headers: {
|
|
35
48
|
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
36
49
|
...(auth ? { authorization: `Bearer ${auth}` } : {}),
|
|
37
|
-
...
|
|
50
|
+
...visitor,
|
|
38
51
|
},
|
|
39
52
|
body: body !== undefined ? JSON.stringify(body) : null,
|
|
40
53
|
});
|